1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2012, 2013 Zimbra Software, LLC. 5 * 6 * The contents of this file are subject to the Zimbra Public License 7 * Version 1.4 ("License"); you may not use this file except in 8 * compliance with the License. You may obtain a copy of the License at 9 * http://www.zimbra.com/license. 10 * 11 * Software distributed under the License is distributed on an "AS IS" 12 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. 13 * ***** END LICENSE BLOCK ***** 14 */ 15 // FILE IS GENERATED BY COMBINING THE SOURCES IN THE "classes" DIRECTORY SO DON'T MODIFY THIS FILE DIRECTLY 16 (function(win) { 17 var whiteSpaceRe = /^\s*|\s*$/g, 18 undef, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1'; 19 20 var tinymce = { 21 majorVersion : '3', 22 23 minorVersion : '5.4.1', 24 25 releaseDate : '2012-06-24', 26 27 _init : function() { 28 var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v; 29 30 t.isOpera = win.opera && opera.buildNumber; 31 32 t.isWebKit = /WebKit/.test(ua); 33 34 t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName); 35 36 t.isIE6 = t.isIE && /MSIE [56]/.test(ua); 37 38 t.isIE7 = t.isIE && /MSIE [7]/.test(ua); 39 40 t.isIE8 = t.isIE && /MSIE [8]/.test(ua); 41 42 t.isIE9 = t.isIE && /MSIE [9]/.test(ua); 43 44 t.isGecko = !t.isWebKit && /Gecko/.test(ua); 45 46 t.isMac = ua.indexOf('Mac') != -1; 47 48 t.isAir = /adobeair/i.test(ua); 49 50 t.isIDevice = /(iPad|iPhone)/.test(ua); 51 52 t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534; 53 54 // TinyMCE .NET webcontrol might be setting the values for TinyMCE 55 if (win.tinyMCEPreInit) { 56 t.suffix = tinyMCEPreInit.suffix; 57 t.baseURL = tinyMCEPreInit.base; 58 t.query = tinyMCEPreInit.query; 59 return; 60 } 61 62 // Get suffix and base 63 t.suffix = ''; 64 65 // If base element found, add that infront of baseURL 66 nl = d.getElementsByTagName('base'); 67 for (i=0; i<nl.length; i++) { 68 v = nl[i].href; 69 if (v) { 70 // Host only value like http://site.com or http://site.com:8008 71 if (/^https?:\/\/[^\/]+$/.test(v)) 72 v += '/'; 73 74 base = v ? v.match(/.*\//)[0] : ''; // Get only directory 75 } 76 } 77 78 function getBase(n) { 79 if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) { 80 if (/_(src|dev)\.js/g.test(n.src)) 81 t.suffix = '_src'; 82 83 if ((p = n.src.indexOf('?')) != -1) 84 t.query = n.src.substring(p + 1); 85 86 t.baseURL = n.src.substring(0, n.src.lastIndexOf('/')); 87 88 // If path to script is relative and a base href was found add that one infront 89 // the src property will always be an absolute one on non IE browsers and IE 8 90 // so this logic will basically only be executed on older IE versions 91 if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0) 92 t.baseURL = base + t.baseURL; 93 94 return t.baseURL; 95 } 96 97 return null; 98 }; 99 100 // Check document 101 nl = d.getElementsByTagName('script'); 102 for (i=0; i<nl.length; i++) { 103 if (getBase(nl[i])) 104 return; 105 } 106 107 // Check head 108 n = d.getElementsByTagName('head')[0]; 109 if (n) { 110 nl = n.getElementsByTagName('script'); 111 for (i=0; i<nl.length; i++) { 112 if (getBase(nl[i])) 113 return; 114 } 115 } 116 117 return; 118 }, 119 120 is : function(o, t) { 121 if (!t) 122 return o !== undef; 123 124 if (t == 'array' && (o.hasOwnProperty && o instanceof Array)) 125 return true; 126 127 return typeof(o) == t; 128 }, 129 130 makeMap : function(items, delim, map) { 131 var i; 132 133 items = items || []; 134 delim = delim || ','; 135 136 if (typeof(items) == "string") 137 items = items.split(delim); 138 139 map = map || {}; 140 141 i = items.length; 142 while (i--) 143 map[items[i]] = {}; 144 145 return map; 146 }, 147 148 each : function(o, cb, s) { 149 var n, l; 150 151 if (!o) 152 return 0; 153 154 s = s || o; 155 156 if (o.length !== undef) { 157 // Indexed arrays, needed for Safari 158 for (n=0, l = o.length; n < l; n++) { 159 if (cb.call(s, o[n], n, o) === false) 160 return 0; 161 } 162 } else { 163 // Hashtables 164 for (n in o) { 165 if (o.hasOwnProperty(n)) { 166 if (cb.call(s, o[n], n, o) === false) 167 return 0; 168 } 169 } 170 } 171 172 return 1; 173 }, 174 175 176 map : function(a, f) { 177 var o = []; 178 179 tinymce.each(a, function(v) { 180 o.push(f(v)); 181 }); 182 183 return o; 184 }, 185 186 grep : function(a, f) { 187 var o = []; 188 189 tinymce.each(a, function(v) { 190 if (!f || f(v)) 191 o.push(v); 192 }); 193 194 return o; 195 }, 196 197 inArray : function(a, v) { 198 var i, l; 199 200 if (a) { 201 for (i = 0, l = a.length; i < l; i++) { 202 if (a[i] === v) 203 return i; 204 } 205 } 206 207 return -1; 208 }, 209 210 extend : function(obj, ext) { 211 var i, l, name, args = arguments, value; 212 213 for (i = 1, l = args.length; i < l; i++) { 214 ext = args[i]; 215 for (name in ext) { 216 if (ext.hasOwnProperty(name)) { 217 value = ext[name]; 218 219 if (value !== undef) { 220 obj[name] = value; 221 } 222 } 223 } 224 } 225 226 return obj; 227 }, 228 229 230 trim : function(s) { 231 return (s ? '' + s : '').replace(whiteSpaceRe, ''); 232 }, 233 234 create : function(s, p, root) { 235 var t = this, sp, ns, cn, scn, c, de = 0; 236 237 // Parse : <prefix> <class>:<super class> 238 s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s); 239 cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name 240 241 // Create namespace for new class 242 ns = t.createNS(s[3].replace(/\.\w+$/, ''), root); 243 244 // Class already exists 245 if (ns[cn]) 246 return; 247 248 // Make pure static class 249 if (s[2] == 'static') { 250 ns[cn] = p; 251 252 if (this.onCreate) 253 this.onCreate(s[2], s[3], ns[cn]); 254 255 return; 256 } 257 258 // Create default constructor 259 if (!p[cn]) { 260 p[cn] = function() {}; 261 de = 1; 262 } 263 264 // Add constructor and methods 265 ns[cn] = p[cn]; 266 t.extend(ns[cn].prototype, p); 267 268 // Extend 269 if (s[5]) { 270 sp = t.resolve(s[5]).prototype; 271 scn = s[5].match(/\.(\w+)$/i)[1]; // Class name 272 273 // Extend constructor 274 c = ns[cn]; 275 if (de) { 276 // Add passthrough constructor 277 ns[cn] = function() { 278 return sp[scn].apply(this, arguments); 279 }; 280 } else { 281 // Add inherit constructor 282 ns[cn] = function() { 283 this.parent = sp[scn]; 284 return c.apply(this, arguments); 285 }; 286 } 287 ns[cn].prototype[cn] = ns[cn]; 288 289 // Add super methods 290 t.each(sp, function(f, n) { 291 ns[cn].prototype[n] = sp[n]; 292 }); 293 294 // Add overridden methods 295 t.each(p, function(f, n) { 296 // Extend methods if needed 297 if (sp[n]) { 298 ns[cn].prototype[n] = function() { 299 this.parent = sp[n]; 300 return f.apply(this, arguments); 301 }; 302 } else { 303 if (n != cn) 304 ns[cn].prototype[n] = f; 305 } 306 }); 307 } 308 309 // Add static methods 310 t.each(p['static'], function(f, n) { 311 ns[cn][n] = f; 312 }); 313 314 if (this.onCreate) 315 this.onCreate(s[2], s[3], ns[cn].prototype); 316 }, 317 318 walk : function(o, f, n, s) { 319 s = s || this; 320 321 if (o) { 322 if (n) 323 o = o[n]; 324 325 tinymce.each(o, function(o, i) { 326 if (f.call(s, o, i, n) === false) 327 return false; 328 329 tinymce.walk(o, f, n, s); 330 }); 331 } 332 }, 333 334 createNS : function(n, o) { 335 var i, v; 336 337 o = o || win; 338 339 n = n.split('.'); 340 for (i=0; i<n.length; i++) { 341 v = n[i]; 342 343 if (!o[v]) 344 o[v] = {}; 345 346 o = o[v]; 347 } 348 349 return o; 350 }, 351 352 resolve : function(n, o) { 353 var i, l; 354 355 o = o || win; 356 357 n = n.split('.'); 358 for (i = 0, l = n.length; i < l; i++) { 359 o = o[n[i]]; 360 361 if (!o) 362 break; 363 } 364 365 return o; 366 }, 367 368 addUnload : function(f, s) { 369 var t = this, unload; 370 371 unload = function() { 372 var li = t.unloads, o, n; 373 374 if (li) { 375 // Call unload handlers 376 for (n in li) { 377 o = li[n]; 378 379 if (o && o.func) 380 o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy 381 } 382 383 // Detach unload function 384 if (win.detachEvent) { 385 win.detachEvent('onbeforeunload', fakeUnload); 386 win.detachEvent('onunload', unload); 387 } else if (win.removeEventListener) 388 win.removeEventListener('unload', unload, false); 389 390 // Destroy references 391 t.unloads = o = li = w = unload = 0; 392 393 // Run garbarge collector on IE 394 if (win.CollectGarbage) 395 CollectGarbage(); 396 } 397 }; 398 399 function fakeUnload() { 400 var d = document; 401 402 function stop() { 403 // Prevent memory leak 404 d.detachEvent('onstop', stop); 405 406 // Call unload handler 407 if (unload) 408 unload(); 409 410 d = 0; 411 }; 412 413 // Is there things still loading, then do some magic 414 if (d.readyState == 'interactive') { 415 // Fire unload when the currently loading page is stopped 416 if (d) 417 d.attachEvent('onstop', stop); 418 419 // Remove onstop listener after a while to prevent the unload function 420 // to execute if the user presses cancel in an onbeforeunload 421 // confirm dialog and then presses the browser stop button 422 win.setTimeout(function() { 423 if (d) 424 d.detachEvent('onstop', stop); 425 }, 0); 426 } 427 }; 428 429 f = {func : f, scope : s || this}; 430 431 if (!t.unloads) { 432 // Attach unload handler 433 if (win.attachEvent) { 434 win.attachEvent('onunload', unload); 435 win.attachEvent('onbeforeunload', fakeUnload); 436 } else if (win.addEventListener) 437 win.addEventListener('unload', unload, false); 438 439 // Setup initial unload handler array 440 t.unloads = [f]; 441 } else 442 t.unloads.push(f); 443 444 return f; 445 }, 446 447 removeUnload : function(f) { 448 var u = this.unloads, r = null; 449 450 tinymce.each(u, function(o, i) { 451 if (o && o.func == f) { 452 u.splice(i, 1); 453 r = f; 454 return false; 455 } 456 }); 457 458 return r; 459 }, 460 461 explode : function(s, d) { 462 if (!s || tinymce.is(s, 'array')) { 463 return s; 464 } 465 466 return tinymce.map(s.split(d || ','), tinymce.trim); 467 }, 468 469 _addVer : function(u) { 470 var v; 471 472 if (!this.query) 473 return u; 474 475 v = (u.indexOf('?') == -1 ? '?' : '&') + this.query; 476 477 if (u.indexOf('#') == -1) 478 return u + v; 479 480 return u.replace('#', v + '#'); 481 }, 482 483 // Fix function for IE 9 where regexps isn't working correctly 484 // Todo: remove me once MS fixes the bug 485 _replace : function(find, replace, str) { 486 // On IE9 we have to fake $x replacement 487 if (isRegExpBroken) { 488 return str.replace(find, function() { 489 var val = replace, args = arguments, i; 490 491 for (i = 0; i < args.length - 2; i++) { 492 if (args[i] === undef) { 493 val = val.replace(new RegExp('\\$' + i, 'g'), ''); 494 } else { 495 val = val.replace(new RegExp('\\$' + i, 'g'), args[i]); 496 } 497 } 498 499 return val; 500 }); 501 } 502 503 return str.replace(find, replace); 504 } 505 506 }; 507 508 // Initialize the API 509 tinymce._init(); 510 511 // Expose tinymce namespace to the global namespace (window) 512 win.tinymce = win.tinyMCE = tinymce; 513 514 // Describe the different namespaces 515 516 })(window); 517 518 519 520 (function() { 521 if (!window.Prototype) 522 return alert("Load prototype first!"); 523 524 // Patch in core NS functions 525 tinymce.extend(tinymce, { 526 trim : function(s) {return s ? s.strip() : '';}, 527 inArray : function(a, v) {return a && a.indexOf ? a.indexOf(v) : -1;} 528 }); 529 530 // Patch in functions in various clases 531 // Add a "#ifndefjquery" statement around each core API function you add below 532 var patches = { 533 'tinymce.util.JSON' : { 534 /*serialize : function(o) { 535 return o.toJSON(); 536 }*/ 537 } 538 }; 539 540 // Patch functions after a class is created 541 tinymce.onCreate = function(ty, c, p) { 542 tinymce.extend(p, patches[c]); 543 }; 544 })(); 545 546 547 tinymce.create('tinymce.util.Dispatcher', { 548 scope : null, 549 listeners : null, 550 inDispatch: false, 551 552 Dispatcher : function(scope) { 553 this.scope = scope || this; 554 this.listeners = []; 555 }, 556 557 add : function(callback, scope) { 558 this.listeners.push({cb : callback, scope : scope || this.scope}); 559 560 return callback; 561 }, 562 563 addToTop : function(callback, scope) { 564 var self = this, listener = {cb : callback, scope : scope || self.scope}; 565 566 // Create new listeners if addToTop is executed in a dispatch loop 567 if (self.inDispatch) { 568 self.listeners = [listener].concat(self.listeners); 569 } else { 570 self.listeners.unshift(listener); 571 } 572 573 return callback; 574 }, 575 576 remove : function(callback) { 577 var listeners = this.listeners, output = null; 578 579 tinymce.each(listeners, function(listener, i) { 580 if (callback == listener.cb) { 581 output = listener; 582 listeners.splice(i, 1); 583 return false; 584 } 585 }); 586 587 return output; 588 }, 589 590 dispatch : function() { 591 var self = this, returnValue, args = arguments, i, listeners = self.listeners, listener; 592 593 self.inDispatch = true; 594 595 // Needs to be a real loop since the listener count might change while looping 596 // And this is also more efficient 597 for (i = 0; i < listeners.length; i++) { 598 listener = listeners[i]; 599 returnValue = listener.cb.apply(listener.scope, args.length > 0 ? args : [listener.scope]); 600 601 if (returnValue === false) 602 break; 603 } 604 605 self.inDispatch = false; 606 607 return returnValue; 608 } 609 610 }); 611 612 (function() { 613 var each = tinymce.each; 614 615 tinymce.create('tinymce.util.URI', { 616 URI : function(u, s) { 617 var t = this, o, a, b, base_url; 618 619 // Trim whitespace 620 u = tinymce.trim(u); 621 622 // Default settings 623 s = t.settings = s || {}; 624 625 // Strange app protocol that isn't http/https or local anchor 626 // For example: mailto,skype,tel etc. 627 if (/^([\w\-]+):([^\/]{2})/i.test(u) || /^\s*#/.test(u)) { 628 t.source = u; 629 return; 630 } 631 632 // Absolute path with no host, fake host and protocol 633 if (u.indexOf('/') === 0 && u.indexOf('//') !== 0) 634 u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u; 635 636 // Relative path http:// or protocol relative //path 637 if (!/^[\w\-]*:?\/\//.test(u)) { 638 base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory; 639 u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u); 640 } 641 642 // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri) 643 u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something 644 u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u); 645 each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) { 646 var s = u[i]; 647 648 // Zope 3 workaround, they use @@something 649 if (s) 650 s = s.replace(/\(mce_at\)/g, '@@'); 651 652 t[v] = s; 653 }); 654 655 b = s.base_uri; 656 if (b) { 657 if (!t.protocol) 658 t.protocol = b.protocol; 659 660 if (!t.userInfo) 661 t.userInfo = b.userInfo; 662 663 if (!t.port && t.host === 'mce_host') 664 t.port = b.port; 665 666 if (!t.host || t.host === 'mce_host') 667 t.host = b.host; 668 669 t.source = ''; 670 } 671 672 //t.path = t.path || '/'; 673 }, 674 675 setPath : function(p) { 676 var t = this; 677 678 p = /^(.*?)\/?(\w+)?$/.exec(p); 679 680 // Update path parts 681 t.path = p[0]; 682 t.directory = p[1]; 683 t.file = p[2]; 684 685 // Rebuild source 686 t.source = ''; 687 t.getURI(); 688 }, 689 690 toRelative : function(u) { 691 var t = this, o; 692 693 if (u === "./") 694 return u; 695 696 u = new tinymce.util.URI(u, {base_uri : t}); 697 698 // Not on same domain/port or protocol 699 if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol) 700 return u.getURI(); 701 702 var tu = t.getURI(), uu = u.getURI(); 703 704 // Allow usage of the base_uri when relative_urls = true 705 if(tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu)) 706 return tu; 707 708 o = t.toRelPath(t.path, u.path); 709 710 // Add query 711 if (u.query) 712 o += '?' + u.query; 713 714 // Add anchor 715 if (u.anchor) 716 o += '#' + u.anchor; 717 718 return o; 719 }, 720 721 toAbsolute : function(u, nh) { 722 u = new tinymce.util.URI(u, {base_uri : this}); 723 724 return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0); 725 }, 726 727 toRelPath : function(base, path) { 728 var items, bp = 0, out = '', i, l; 729 730 // Split the paths 731 base = base.substring(0, base.lastIndexOf('/')); 732 base = base.split('/'); 733 items = path.split('/'); 734 735 if (base.length >= items.length) { 736 for (i = 0, l = base.length; i < l; i++) { 737 if (i >= items.length || base[i] != items[i]) { 738 bp = i + 1; 739 break; 740 } 741 } 742 } 743 744 if (base.length < items.length) { 745 for (i = 0, l = items.length; i < l; i++) { 746 if (i >= base.length || base[i] != items[i]) { 747 bp = i + 1; 748 break; 749 } 750 } 751 } 752 753 if (bp === 1) 754 return path; 755 756 for (i = 0, l = base.length - (bp - 1); i < l; i++) 757 out += "../"; 758 759 for (i = bp - 1, l = items.length; i < l; i++) { 760 if (i != bp - 1) 761 out += "/" + items[i]; 762 else 763 out += items[i]; 764 } 765 766 return out; 767 }, 768 769 toAbsPath : function(base, path) { 770 var i, nb = 0, o = [], tr, outPath; 771 772 // Split paths 773 tr = /\/$/.test(path) ? '/' : ''; 774 base = base.split('/'); 775 path = path.split('/'); 776 777 // Remove empty chunks 778 each(base, function(k) { 779 if (k) 780 o.push(k); 781 }); 782 783 base = o; 784 785 // Merge relURLParts chunks 786 for (i = path.length - 1, o = []; i >= 0; i--) { 787 // Ignore empty or . 788 if (path[i].length === 0 || path[i] === ".") 789 continue; 790 791 // Is parent 792 if (path[i] === '..') { 793 nb++; 794 continue; 795 } 796 797 // Move up 798 if (nb > 0) { 799 nb--; 800 continue; 801 } 802 803 o.push(path[i]); 804 } 805 806 i = base.length - nb; 807 808 // If /a/b/c or / 809 if (i <= 0) 810 outPath = o.reverse().join('/'); 811 else 812 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/'); 813 814 // Add front / if it's needed 815 if (outPath.indexOf('/') !== 0) 816 outPath = '/' + outPath; 817 818 // Add traling / if it's needed 819 if (tr && outPath.lastIndexOf('/') !== outPath.length - 1) 820 outPath += tr; 821 822 return outPath; 823 }, 824 825 getURI : function(nh) { 826 var s, t = this; 827 828 // Rebuild source 829 if (!t.source || nh) { 830 s = ''; 831 832 if (!nh) { 833 if (t.protocol) 834 s += t.protocol + '://'; 835 836 if (t.userInfo) 837 s += t.userInfo + '@'; 838 839 if (t.host) 840 s += t.host; 841 842 if (t.port) 843 s += ':' + t.port; 844 } 845 846 if (t.path) 847 s += t.path; 848 849 if (t.query) 850 s += '?' + t.query; 851 852 if (t.anchor) 853 s += '#' + t.anchor; 854 855 t.source = s; 856 } 857 858 return t.source; 859 } 860 }); 861 })(); 862 863 (function() { 864 var each = tinymce.each; 865 866 tinymce.create('static tinymce.util.Cookie', { 867 getHash : function(n) { 868 var v = this.get(n), h; 869 870 if (v) { 871 each(v.split('&'), function(v) { 872 v = v.split('='); 873 h = h || {}; 874 h[unescape(v[0])] = unescape(v[1]); 875 }); 876 } 877 878 return h; 879 }, 880 881 setHash : function(n, v, e, p, d, s) { 882 var o = ''; 883 884 each(v, function(v, k) { 885 o += (!o ? '' : '&') + escape(k) + '=' + escape(v); 886 }); 887 888 this.set(n, o, e, p, d, s); 889 }, 890 891 get : function(n) { 892 var c = document.cookie, e, p = n + "=", b; 893 894 // Strict mode 895 if (!c) 896 return; 897 898 b = c.indexOf("; " + p); 899 900 if (b == -1) { 901 b = c.indexOf(p); 902 903 if (b !== 0) 904 return null; 905 } else 906 b += 2; 907 908 e = c.indexOf(";", b); 909 910 if (e == -1) 911 e = c.length; 912 913 return unescape(c.substring(b + p.length, e)); 914 }, 915 916 set : function(n, v, e, p, d, s) { 917 document.cookie = n + "=" + escape(v) + 918 ((e) ? "; expires=" + e.toGMTString() : "") + 919 ((p) ? "; path=" + escape(p) : "") + 920 ((d) ? "; domain=" + d : "") + 921 ((s) ? "; secure" : ""); 922 }, 923 924 remove : function(name, path, domain) { 925 var date = new Date(); 926 927 date.setTime(date.getTime() - 1000); 928 929 this.set(name, '', date, path, domain); 930 } 931 }); 932 })(); 933 934 (function() { 935 function serialize(o, quote) { 936 var i, v, t, name; 937 938 quote = quote || '"'; 939 940 if (o == null) 941 return 'null'; 942 943 t = typeof o; 944 945 if (t == 'string') { 946 v = '\bb\tt\nn\ff\rr\""\'\'\\\\'; 947 948 return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) { 949 // Make sure single quotes never get encoded inside double quotes for JSON compatibility 950 if (quote === '"' && a === "'") 951 return a; 952 953 i = v.indexOf(b); 954 955 if (i + 1) 956 return '\\' + v.charAt(i + 1); 957 958 a = b.charCodeAt().toString(16); 959 960 return '\\u' + '0000'.substring(a.length) + a; 961 }) + quote; 962 } 963 964 if (t == 'object') { 965 if (o.hasOwnProperty && o instanceof Array) { 966 for (i=0, v = '['; i<o.length; i++) 967 v += (i > 0 ? ',' : '') + serialize(o[i], quote); 968 969 return v + ']'; 970 } 971 972 v = '{'; 973 974 for (name in o) { 975 if (o.hasOwnProperty(name)) { 976 v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name + quote +':' + serialize(o[name], quote) : ''; 977 } 978 } 979 980 return v + '}'; 981 } 982 983 return '' + o; 984 }; 985 986 tinymce.util.JSON = { 987 serialize: serialize, 988 989 parse: function(s) { 990 try { 991 return eval('(' + s + ')'); 992 } catch (ex) { 993 // Ignore 994 } 995 } 996 997 }; 998 })(); 999 1000 tinymce.create('static tinymce.util.XHR', { 1001 send : function(o) { 1002 var x, t, w = window, c = 0; 1003 1004 function ready() { 1005 if (!o.async || x.readyState == 4 || c++ > 10000) { 1006 if (o.success && c < 10000 && x.status == 200) 1007 o.success.call(o.success_scope, '' + x.responseText, x, o); 1008 else if (o.error) 1009 o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o); 1010 1011 x = null; 1012 } else 1013 w.setTimeout(ready, 10); 1014 }; 1015 1016 // Default settings 1017 o.scope = o.scope || this; 1018 o.success_scope = o.success_scope || o.scope; 1019 o.error_scope = o.error_scope || o.scope; 1020 o.async = o.async === false ? false : true; 1021 o.data = o.data || ''; 1022 1023 function get(s) { 1024 x = 0; 1025 1026 try { 1027 x = new ActiveXObject(s); 1028 } catch (ex) { 1029 } 1030 1031 return x; 1032 }; 1033 1034 x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP'); 1035 1036 if (x) { 1037 if (x.overrideMimeType) 1038 x.overrideMimeType(o.content_type); 1039 1040 x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async); 1041 1042 if (o.content_type) 1043 x.setRequestHeader('Content-Type', o.content_type); 1044 1045 x.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 1046 1047 x.send(o.data); 1048 1049 // Syncronous request 1050 if (!o.async) 1051 return ready(); 1052 1053 // Wait for response, onReadyStateChange can not be used since it leaks memory in IE 1054 t = w.setTimeout(ready, 10); 1055 } 1056 } 1057 }); 1058 1059 (function() { 1060 var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR; 1061 1062 tinymce.create('tinymce.util.JSONRequest', { 1063 JSONRequest : function(s) { 1064 this.settings = extend({ 1065 }, s); 1066 this.count = 0; 1067 }, 1068 1069 send : function(o) { 1070 var ecb = o.error, scb = o.success; 1071 1072 o = extend(this.settings, o); 1073 1074 o.success = function(c, x) { 1075 c = JSON.parse(c); 1076 1077 if (typeof(c) == 'undefined') { 1078 c = { 1079 error : 'JSON Parse error.' 1080 }; 1081 } 1082 1083 if (c.error) 1084 ecb.call(o.error_scope || o.scope, c.error, x); 1085 else 1086 scb.call(o.success_scope || o.scope, c.result); 1087 }; 1088 1089 o.error = function(ty, x) { 1090 if (ecb) 1091 ecb.call(o.error_scope || o.scope, ty, x); 1092 }; 1093 1094 o.data = JSON.serialize({ 1095 id : o.id || 'c' + (this.count++), 1096 method : o.method, 1097 params : o.params 1098 }); 1099 1100 // JSON content type for Ruby on rails. Bug: #1883287 1101 o.content_type = 'application/json'; 1102 1103 XHR.send(o); 1104 }, 1105 1106 'static' : { 1107 sendRPC : function(o) { 1108 return new tinymce.util.JSONRequest().send(o); 1109 } 1110 } 1111 }); 1112 }()); 1113 (function(tinymce){ 1114 tinymce.VK = { 1115 BACKSPACE: 8, 1116 DELETE: 46, 1117 DOWN: 40, 1118 ENTER: 13, 1119 LEFT: 37, 1120 RIGHT: 39, 1121 SPACEBAR: 32, 1122 TAB: 9, 1123 UP: 38, 1124 1125 modifierPressed: function (e) { 1126 return e.shiftKey || e.ctrlKey || e.altKey; 1127 }, 1128 1129 metaKeyPressed: function(e) { 1130 return tinymce.isMac ? e.metaKey : e.ctrlKey; 1131 } 1132 }; 1133 })(tinymce); 1134 1135 tinymce.util.Quirks = function(editor) { 1136 var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection, settings = editor.settings; 1137 1138 function setEditorCommandState(cmd, state) { 1139 try { 1140 editor.getDoc().execCommand(cmd, false, state); 1141 } catch (ex) { 1142 // Ignore 1143 } 1144 } 1145 1146 function getDocumentMode() { 1147 var documentMode = editor.getDoc().documentMode; 1148 1149 return documentMode ? documentMode : 6; 1150 }; 1151 1152 function cleanupStylesWhenDeleting() { 1153 function removeMergedFormatSpans(isDelete) { 1154 var rng, blockElm, node, clonedSpan; 1155 1156 rng = selection.getRng(); 1157 1158 // Find root block 1159 blockElm = dom.getParent(rng.startContainer, dom.isBlock); 1160 1161 // On delete clone the root span of the next block element 1162 if (isDelete) 1163 blockElm = dom.getNext(blockElm, dom.isBlock); 1164 1165 // Locate root span element and clone it since it would otherwise get merged by the "apple-style-span" on delete/backspace 1166 if (blockElm) { 1167 node = blockElm.firstChild; 1168 1169 // Ignore empty text nodes 1170 while (node && node.nodeType == 3 && node.nodeValue.length === 0) 1171 node = node.nextSibling; 1172 1173 if (node && node.nodeName === 'SPAN') { 1174 clonedSpan = node.cloneNode(false); 1175 } 1176 } 1177 1178 // Do the backspace/delete action 1179 editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null); 1180 1181 // Find all odd apple-style-spans 1182 blockElm = dom.getParent(rng.startContainer, dom.isBlock); 1183 tinymce.each(dom.select('span.Apple-style-span,font.Apple-style-span', blockElm), function(span) { 1184 var bm = selection.getBookmark(); 1185 1186 if (clonedSpan) { 1187 dom.replace(clonedSpan.cloneNode(false), span, true); 1188 } else { 1189 dom.remove(span, true); 1190 } 1191 1192 // Restore the selection 1193 selection.moveToBookmark(bm); 1194 }); 1195 }; 1196 1197 editor.onKeyDown.add(function(editor, e) { 1198 var isDelete; 1199 1200 isDelete = e.keyCode == DELETE; 1201 if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) { 1202 e.preventDefault(); 1203 removeMergedFormatSpans(isDelete); 1204 } 1205 }); 1206 1207 editor.addCommand('Delete', function() {removeMergedFormatSpans();}); 1208 }; 1209 1210 function emptyEditorWhenDeleting() { 1211 function serializeRng(rng) { 1212 var body = dom.create("body"); 1213 var contents = rng.cloneContents(); 1214 body.appendChild(contents); 1215 return selection.serializer.serialize(body, {format: 'html'}); 1216 } 1217 1218 function allContentsSelected(rng) { 1219 var selection = serializeRng(rng); 1220 1221 var allRng = dom.createRng(); 1222 allRng.selectNode(editor.getBody()); 1223 1224 var allSelection = serializeRng(allRng);//console.log(selection, "----", allSelection); 1225 return selection === allSelection; 1226 } 1227 1228 editor.onKeyDown.add(function(editor, e) { 1229 var keyCode = e.keyCode, isCollapsed; 1230 1231 // Empty the editor if it's needed for example backspace at <p><b>|</b></p> 1232 if (!e.isDefaultPrevented() && (keyCode == DELETE || keyCode == BACKSPACE)) { 1233 isCollapsed = editor.selection.isCollapsed(); 1234 1235 // Selection is collapsed but the editor isn't empty 1236 if (isCollapsed && !dom.isEmpty(editor.getBody())) { 1237 return; 1238 } 1239 1240 // IE deletes all contents correctly when everything is selected 1241 if (tinymce.isIE && !isCollapsed) { 1242 return; 1243 } 1244 1245 // Selection isn't collapsed but not all the contents is selected 1246 if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) { 1247 return; 1248 } 1249 1250 // Manually empty the editor 1251 editor.setContent(''); 1252 editor.selection.setCursorLocation(editor.getBody(), 0); 1253 editor.nodeChanged(); 1254 } 1255 }); 1256 }; 1257 1258 function selectAll() { 1259 editor.onKeyDown.add(function(editor, e) { 1260 if (e.keyCode == 65 && VK.metaKeyPressed(e)) { 1261 e.preventDefault(); 1262 editor.execCommand('SelectAll'); 1263 } 1264 }); 1265 }; 1266 1267 function inputMethodFocus() { 1268 if (!editor.settings.content_editable) { 1269 // Case 1 IME doesn't initialize if you focus the document 1270 dom.bind(editor.getDoc(), 'focusin', function(e) { 1271 selection.setRng(selection.getRng()); 1272 }); 1273 1274 // Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event 1275 dom.bind(editor.getDoc(), 'mousedown', function(e) { 1276 if (e.target == editor.getDoc().documentElement) { 1277 editor.getWin().focus(); 1278 selection.setRng(selection.getRng()); 1279 } 1280 }); 1281 } 1282 }; 1283 1284 function removeHrOnBackspace() { 1285 editor.onKeyDown.add(function(editor, e) { 1286 if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) { 1287 if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) { 1288 var node = selection.getNode(); 1289 var previousSibling = node.previousSibling; 1290 1291 if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") { 1292 dom.remove(previousSibling); 1293 tinymce.dom.Event.cancel(e); 1294 } 1295 } 1296 } 1297 }) 1298 } 1299 1300 function focusBody() { 1301 // Fix for a focus bug in FF 3.x where the body element 1302 // wouldn't get proper focus if the user clicked on the HTML element 1303 if (!Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4 1304 editor.onMouseDown.add(function(editor, e) { 1305 if (e.target.nodeName === "HTML") { 1306 var body = editor.getBody(); 1307 1308 // Blur the body it's focused but not correctly focused 1309 body.blur(); 1310 1311 // Refocus the body after a little while 1312 setTimeout(function() { 1313 body.focus(); 1314 }, 0); 1315 } 1316 }); 1317 } 1318 }; 1319 1320 function selectControlElements() { 1321 editor.onClick.add(function(editor, e) { 1322 e = e.target; 1323 1324 // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250 1325 // WebKit can't even do simple things like selecting an image 1326 // Needs tobe the setBaseAndExtend or it will fail to select floated images 1327 if (/^(IMG|HR)$/.test(e.nodeName)) { 1328 selection.getSel().setBaseAndExtent(e, 0, e, 1); 1329 } 1330 1331 if (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor')) { 1332 selection.select(e); 1333 } 1334 1335 editor.nodeChanged(); 1336 }); 1337 }; 1338 1339 function removeStylesWhenDeletingAccrossBlockElements() { 1340 function getAttributeApplyFunction() { 1341 var template = dom.getAttribs(selection.getStart().cloneNode(false)); 1342 1343 return function() { 1344 var target = selection.getStart(); 1345 1346 if (target !== editor.getBody()) { 1347 dom.setAttrib(target, "style", null); 1348 1349 tinymce.each(template, function(attr) { 1350 target.setAttributeNode(attr.cloneNode(true)); 1351 }); 1352 } 1353 }; 1354 } 1355 1356 function isSelectionAcrossElements() { 1357 return !selection.isCollapsed() && selection.getStart() != selection.getEnd(); 1358 } 1359 1360 function blockEvent(editor, e) { 1361 e.preventDefault(); 1362 return false; 1363 } 1364 1365 editor.onKeyPress.add(function(editor, e) { 1366 var applyAttributes; 1367 1368 if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) { 1369 applyAttributes = getAttributeApplyFunction(); 1370 editor.getDoc().execCommand('delete', false, null); 1371 applyAttributes(); 1372 e.preventDefault(); 1373 return false; 1374 } 1375 }); 1376 1377 dom.bind(editor.getDoc(), 'cut', function(e) { 1378 var applyAttributes; 1379 1380 if (isSelectionAcrossElements()) { 1381 applyAttributes = getAttributeApplyFunction(); 1382 editor.onKeyUp.addToTop(blockEvent); 1383 1384 setTimeout(function() { 1385 applyAttributes(); 1386 editor.onKeyUp.remove(blockEvent); 1387 }, 0); 1388 } 1389 }); 1390 } 1391 1392 function selectionChangeNodeChanged() { 1393 var lastRng, selectionTimer; 1394 1395 dom.bind(editor.getDoc(), 'selectionchange', function() { 1396 if (selectionTimer) { 1397 clearTimeout(selectionTimer); 1398 selectionTimer = 0; 1399 } 1400 1401 selectionTimer = window.setTimeout(function() { 1402 var rng = selection.getRng(); 1403 1404 // Compare the ranges to see if it was a real change or not 1405 if (!lastRng || !tinymce.dom.RangeUtils.compareRanges(rng, lastRng)) { 1406 editor.nodeChanged(); 1407 lastRng = rng; 1408 } 1409 }, 50); 1410 }); 1411 } 1412 1413 function ensureBodyHasRoleApplication() { 1414 document.body.setAttribute("role", "application"); 1415 } 1416 1417 function disableBackspaceIntoATable() { 1418 editor.onKeyDown.add(function(editor, e) { 1419 if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) { 1420 if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) { 1421 var previousSibling = selection.getNode().previousSibling; 1422 if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") { 1423 return tinymce.dom.Event.cancel(e); 1424 } 1425 } 1426 } 1427 }) 1428 } 1429 1430 function addNewLinesBeforeBrInPre() { 1431 // IE8+ rendering mode does the right thing with BR in PRE 1432 if (getDocumentMode() > 7) { 1433 return; 1434 } 1435 1436 // Enable display: none in area and add a specific class that hides all BR elements in PRE to 1437 // avoid the caret from getting stuck at the BR elements while pressing the right arrow key 1438 setEditorCommandState('RespectVisibilityInDesign', true); 1439 editor.contentStyles.push('.mceHideBrInPre pre br {display: none}'); 1440 dom.addClass(editor.getBody(), 'mceHideBrInPre'); 1441 1442 // Adds a \n before all BR elements in PRE to get them visual 1443 editor.parser.addNodeFilter('pre', function(nodes, name) { 1444 var i = nodes.length, brNodes, j, brElm, sibling; 1445 1446 while (i--) { 1447 brNodes = nodes[i].getAll('br'); 1448 j = brNodes.length; 1449 while (j--) { 1450 brElm = brNodes[j]; 1451 1452 // Add \n before BR in PRE elements on older IE:s so the new lines get rendered 1453 sibling = brElm.prev; 1454 if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') { 1455 sibling.value += '\n'; 1456 } else { 1457 brElm.parent.insert(new tinymce.html.Node('#text', 3), brElm, true).value = '\n'; 1458 } 1459 } 1460 } 1461 }); 1462 1463 // Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible 1464 editor.serializer.addNodeFilter('pre', function(nodes, name) { 1465 var i = nodes.length, brNodes, j, brElm, sibling; 1466 1467 while (i--) { 1468 brNodes = nodes[i].getAll('br'); 1469 j = brNodes.length; 1470 while (j--) { 1471 brElm = brNodes[j]; 1472 sibling = brElm.prev; 1473 if (sibling && sibling.type == 3) { 1474 sibling.value = sibling.value.replace(/\r?\n$/, ''); 1475 } 1476 } 1477 } 1478 }); 1479 } 1480 1481 function removePreSerializedStylesWhenSelectingControls() { 1482 dom.bind(editor.getBody(), 'mouseup', function(e) { 1483 var value, node = selection.getNode(); 1484 1485 // Moved styles to attributes on IMG eements 1486 if (node.nodeName == 'IMG') { 1487 // Convert style width to width attribute 1488 if (value = dom.getStyle(node, 'width')) { 1489 dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, '')); 1490 dom.setStyle(node, 'width', ''); 1491 } 1492 1493 // Convert style height to height attribute 1494 if (value = dom.getStyle(node, 'height')) { 1495 dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, '')); 1496 dom.setStyle(node, 'height', ''); 1497 } 1498 } 1499 }); 1500 } 1501 1502 function keepInlineElementOnDeleteBackspace() { 1503 editor.onKeyDown.add(function(editor, e) { 1504 var isDelete, rng, container, offset, brElm, sibling, collapsed; 1505 1506 isDelete = e.keyCode == DELETE; 1507 if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) { 1508 rng = selection.getRng(); 1509 container = rng.startContainer; 1510 offset = rng.startOffset; 1511 collapsed = rng.collapsed; 1512 1513 // Override delete if the start container is a text node and is at the beginning of text or 1514 // just before/after the last character to be deleted in collapsed mode 1515 if (container.nodeType == 3 && container.nodeValue.length > 0 && ((offset === 0 && !collapsed) || (collapsed && offset === (isDelete ? 0 : 1)))) { 1516 nonEmptyElements = editor.schema.getNonEmptyElements(); 1517 1518 // Prevent default logic since it's broken 1519 e.preventDefault(); 1520 1521 // Insert a BR before the text node this will prevent the containing element from being deleted/converted 1522 brElm = dom.create('br', {id: '__tmp'}); 1523 container.parentNode.insertBefore(brElm, container); 1524 1525 // Do the browser delete 1526 editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null); 1527 1528 // Check if the previous sibling is empty after deleting for example: <p><b></b>|</p> 1529 container = selection.getRng().startContainer; 1530 sibling = container.previousSibling; 1531 if (sibling && sibling.nodeType == 1 && !dom.isBlock(sibling) && dom.isEmpty(sibling) && !nonEmptyElements[sibling.nodeName.toLowerCase()]) { 1532 dom.remove(sibling); 1533 } 1534 1535 // Remove the temp element we inserted 1536 dom.remove('__tmp'); 1537 } 1538 } 1539 }); 1540 } 1541 1542 function removeBlockQuoteOnBackSpace() { 1543 // Add block quote deletion handler 1544 editor.onKeyDown.add(function(editor, e) { 1545 var rng, container, offset, root, parent; 1546 1547 if (e.isDefaultPrevented() || e.keyCode != VK.BACKSPACE) { 1548 return; 1549 } 1550 1551 rng = selection.getRng(); 1552 container = rng.startContainer; 1553 offset = rng.startOffset; 1554 root = dom.getRoot(); 1555 parent = container; 1556 1557 if (!rng.collapsed || offset !== 0) { 1558 return; 1559 } 1560 1561 while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) { 1562 parent = parent.parentNode; 1563 } 1564 1565 // Is the cursor at the beginning of a blockquote? 1566 if (parent.tagName === 'BLOCKQUOTE') { 1567 // Remove the blockquote 1568 editor.formatter.toggle('blockquote', null, parent); 1569 1570 // Move the caret to the beginning of container 1571 rng.setStart(container, 0); 1572 rng.setEnd(container, 0); 1573 selection.setRng(rng); 1574 selection.collapse(false); 1575 } 1576 }); 1577 }; 1578 1579 function setGeckoEditingOptions() { 1580 function setOpts() { 1581 editor._refreshContentEditable(); 1582 1583 setEditorCommandState("StyleWithCSS", false); 1584 setEditorCommandState("enableInlineTableEditing", false); 1585 1586 if (!settings.object_resizing) { 1587 setEditorCommandState("enableObjectResizing", false); 1588 } 1589 }; 1590 1591 if (!settings.readonly) { 1592 editor.onBeforeExecCommand.add(setOpts); 1593 editor.onMouseDown.add(setOpts); 1594 } 1595 }; 1596 1597 function addBrAfterLastLinks() { 1598 function fixLinks(editor, o) { 1599 tinymce.each(dom.select('a'), function(node) { 1600 var parentNode = node.parentNode, root = dom.getRoot(); 1601 1602 if (parentNode.lastChild === node) { 1603 while (parentNode && !dom.isBlock(parentNode)) { 1604 if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) { 1605 return; 1606 } 1607 1608 parentNode = parentNode.parentNode; 1609 } 1610 1611 dom.add(parentNode, 'br', {'data-mce-bogus' : 1}); 1612 } 1613 }); 1614 }; 1615 1616 editor.onExecCommand.add(function(editor, cmd) { 1617 if (cmd === 'CreateLink') { 1618 fixLinks(editor); 1619 } 1620 }); 1621 1622 editor.onSetContent.add(selection.onSetContent.add(fixLinks)); 1623 }; 1624 1625 function setDefaultBlockType() { 1626 if (settings.forced_root_block) { 1627 editor.onInit.add(function() { 1628 setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block); 1629 }); 1630 } 1631 } 1632 1633 function removeGhostSelection() { 1634 function repaint(sender, args) { 1635 if (!sender || !args.initial) { 1636 editor.execCommand('mceRepaint'); 1637 } 1638 }; 1639 1640 editor.onUndo.add(repaint); 1641 editor.onRedo.add(repaint); 1642 editor.onSetContent.add(repaint); 1643 }; 1644 1645 function deleteControlItemOnBackSpace() { 1646 editor.onKeyDown.add(function(editor, e) { 1647 var rng; 1648 1649 if (!e.isDefaultPrevented() && e.keyCode == BACKSPACE) { 1650 rng = editor.getDoc().selection.createRange(); 1651 if (rng && rng.item) { 1652 e.preventDefault(); 1653 editor.undoManager.beforeChange(); 1654 dom.remove(rng.item(0)); 1655 editor.undoManager.add(); 1656 } 1657 } 1658 }); 1659 }; 1660 1661 function renderEmptyBlocksFix() { 1662 var emptyBlocksCSS; 1663 1664 // IE10+ 1665 if (getDocumentMode() >= 10) { 1666 emptyBlocksCSS = ''; 1667 tinymce.each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) { 1668 emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty'; 1669 }); 1670 1671 editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}'); 1672 } 1673 }; 1674 1675 function fakeImageResize() { 1676 var mouseDownImg, startX, startY, startW, startH; 1677 1678 if (!settings.object_resizing || settings.webkit_fake_resize === false) { 1679 return; 1680 } 1681 1682 editor.contentStyles.push('.mceResizeImages img {cursor: se-resize !important}'); 1683 1684 function resizeImage(e) { 1685 var deltaX, deltaY, ratio, width, height; 1686 1687 if (mouseDownImg) { 1688 deltaX = e.screenX - startX; 1689 deltaY = e.screenY - startY; 1690 ratio = Math.max((startW + deltaX) / startW, (startH + deltaY) / startH); 1691 1692 // Only update styles if the user draged one pixel or more 1693 if (Math.abs(deltaX) > 1 || Math.abs(deltaY) > 1) { 1694 // Constrain proportions 1695 width = Math.round(startW * ratio); 1696 height = Math.round(startH * ratio); 1697 1698 // Resize by using style or attribute 1699 if (mouseDownImg.style.width) { 1700 dom.setStyle(mouseDownImg, 'width', width); 1701 } else { 1702 dom.setAttrib(mouseDownImg, 'width', width); 1703 } 1704 1705 // Resize by using style or attribute 1706 if (mouseDownImg.style.height) { 1707 dom.setStyle(mouseDownImg, 'height', height); 1708 } else { 1709 dom.setAttrib(mouseDownImg, 'height', height); 1710 } 1711 1712 if (!dom.hasClass(editor.getBody(), 'mceResizeImages')) { 1713 dom.addClass(editor.getBody(), 'mceResizeImages'); 1714 } 1715 } 1716 } 1717 }; 1718 1719 editor.onMouseDown.add(function(editor, e) { 1720 var target = e.target; 1721 1722 if (target.nodeName == "IMG") { 1723 mouseDownImg = target; 1724 startX = e.screenX; 1725 startY = e.screenY; 1726 startW = mouseDownImg.clientWidth; 1727 startH = mouseDownImg.clientHeight; 1728 dom.bind(editor.getDoc(), 'mousemove', resizeImage); 1729 e.preventDefault(); 1730 } 1731 }); 1732 1733 // Unbind events on node change and restore resize cursor 1734 editor.onNodeChange.add(function() { 1735 if (mouseDownImg) { 1736 mouseDownImg = null; 1737 dom.unbind(editor.getDoc(), 'mousemove', resizeImage); 1738 } 1739 1740 if (selection.getNode().nodeName == "IMG") { 1741 dom.addClass(editor.getBody(), 'mceResizeImages'); 1742 } else { 1743 dom.removeClass(editor.getBody(), 'mceResizeImages'); 1744 } 1745 }); 1746 }; 1747 1748 // All browsers 1749 disableBackspaceIntoATable(); 1750 removeBlockQuoteOnBackSpace(); 1751 emptyEditorWhenDeleting(); 1752 1753 // WebKit 1754 if (tinymce.isWebKit) { 1755 keepInlineElementOnDeleteBackspace(); 1756 cleanupStylesWhenDeleting(); 1757 inputMethodFocus(); 1758 selectControlElements(); 1759 setDefaultBlockType(); 1760 1761 // iOS 1762 if (tinymce.isIDevice) { 1763 selectionChangeNodeChanged(); 1764 } else { 1765 fakeImageResize(); 1766 selectAll(); 1767 } 1768 } 1769 1770 // IE 1771 if (tinymce.isIE) { 1772 removeHrOnBackspace(); 1773 ensureBodyHasRoleApplication(); 1774 addNewLinesBeforeBrInPre(); 1775 removePreSerializedStylesWhenSelectingControls(); 1776 deleteControlItemOnBackSpace(); 1777 renderEmptyBlocksFix(); 1778 } 1779 1780 // Gecko 1781 if (tinymce.isGecko) { 1782 removeHrOnBackspace(); 1783 focusBody(); 1784 removeStylesWhenDeletingAccrossBlockElements(); 1785 setGeckoEditingOptions(); 1786 addBrAfterLastLinks(); 1787 removeGhostSelection(); 1788 } 1789 }; 1790 (function(tinymce) { 1791 var namedEntities, baseEntities, reverseEntities, 1792 attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, 1793 textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, 1794 rawCharsRegExp = /[<>&\"\']/g, 1795 entityRegExp = /&(#x|#)?([\w]+);/g, 1796 asciiMap = { 1797 128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020", 1798 135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152", 1799 142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022", 1800 150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A", 1801 156 : "\u0153", 158 : "\u017E", 159 : "\u0178" 1802 }; 1803 1804 // Raw entities 1805 baseEntities = { 1806 '\"' : '"', // Needs to be escaped since the YUI compressor would otherwise break the code 1807 "'" : ''', 1808 '<' : '<', 1809 '>' : '>', 1810 '&' : '&' 1811 }; 1812 1813 // Reverse lookup table for raw entities 1814 reverseEntities = { 1815 '<' : '<', 1816 '>' : '>', 1817 '&' : '&', 1818 '"' : '"', 1819 ''' : "'" 1820 }; 1821 1822 // Decodes text by using the browser 1823 function nativeDecode(text) { 1824 var elm; 1825 1826 elm = document.createElement("div"); 1827 elm.innerHTML = text; 1828 1829 return elm.textContent || elm.innerText || text; 1830 }; 1831 1832 // Build a two way lookup table for the entities 1833 function buildEntitiesLookup(items, radix) { 1834 var i, chr, entity, lookup = {}; 1835 1836 if (items) { 1837 items = items.split(','); 1838 radix = radix || 10; 1839 1840 // Build entities lookup table 1841 for (i = 0; i < items.length; i += 2) { 1842 chr = String.fromCharCode(parseInt(items[i], radix)); 1843 1844 // Only add non base entities 1845 if (!baseEntities[chr]) { 1846 entity = '&' + items[i + 1] + ';'; 1847 lookup[chr] = entity; 1848 lookup[entity] = chr; 1849 } 1850 } 1851 1852 return lookup; 1853 } 1854 }; 1855 1856 // Unpack entities lookup where the numbers are in radix 32 to reduce the size 1857 namedEntities = buildEntitiesLookup( 1858 '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' + 1859 '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' + 1860 '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' + 1861 '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' + 1862 '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' + 1863 '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' + 1864 '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' + 1865 '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' + 1866 '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' + 1867 '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' + 1868 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' + 1869 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' + 1870 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' + 1871 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' + 1872 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' + 1873 '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' + 1874 '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' + 1875 '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' + 1876 '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' + 1877 '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' + 1878 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' + 1879 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' + 1880 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' + 1881 '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' + 1882 '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32); 1883 1884 tinymce.html = tinymce.html || {}; 1885 1886 tinymce.html.Entities = { 1887 encodeRaw : function(text, attr) { 1888 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 1889 return baseEntities[chr] || chr; 1890 }); 1891 }, 1892 1893 encodeAllRaw : function(text) { 1894 return ('' + text).replace(rawCharsRegExp, function(chr) { 1895 return baseEntities[chr] || chr; 1896 }); 1897 }, 1898 1899 encodeNumeric : function(text, attr) { 1900 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 1901 // Multi byte sequence convert it to a single entity 1902 if (chr.length > 1) 1903 return '' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';'; 1904 1905 return baseEntities[chr] || '' + chr.charCodeAt(0) + ';'; 1906 }); 1907 }, 1908 1909 encodeNamed : function(text, attr, entities) { 1910 entities = entities || namedEntities; 1911 1912 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 1913 return baseEntities[chr] || entities[chr] || chr; 1914 }); 1915 }, 1916 1917 getEncodeFunc : function(name, entities) { 1918 var Entities = tinymce.html.Entities; 1919 1920 entities = buildEntitiesLookup(entities) || namedEntities; 1921 1922 function encodeNamedAndNumeric(text, attr) { 1923 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 1924 return baseEntities[chr] || entities[chr] || '' + chr.charCodeAt(0) + ';' || chr; 1925 }); 1926 }; 1927 1928 function encodeCustomNamed(text, attr) { 1929 return Entities.encodeNamed(text, attr, entities); 1930 }; 1931 1932 // Replace + with , to be compatible with previous TinyMCE versions 1933 name = tinymce.makeMap(name.replace(/\+/g, ',')); 1934 1935 // Named and numeric encoder 1936 if (name.named && name.numeric) 1937 return encodeNamedAndNumeric; 1938 1939 // Named encoder 1940 if (name.named) { 1941 // Custom names 1942 if (entities) 1943 return encodeCustomNamed; 1944 1945 return Entities.encodeNamed; 1946 } 1947 1948 // Numeric 1949 if (name.numeric) 1950 return Entities.encodeNumeric; 1951 1952 // Raw encoder 1953 return Entities.encodeRaw; 1954 }, 1955 1956 decode : function(text) { 1957 return text.replace(entityRegExp, function(all, numeric, value) { 1958 if (numeric) { 1959 value = parseInt(value, numeric.length === 2 ? 16 : 10); 1960 1961 // Support upper UTF 1962 if (value > 0xFFFF) { 1963 value -= 0x10000; 1964 1965 return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF)); 1966 } else 1967 return asciiMap[value] || String.fromCharCode(value); 1968 } 1969 1970 return reverseEntities[all] || namedEntities[all] || nativeDecode(all); 1971 }); 1972 } 1973 }; 1974 })(tinymce); 1975 1976 tinymce.html.Styles = function(settings, schema) { 1977 var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi, 1978 urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi, 1979 styleRegExp = /\s*([^:]+):\s*([^;]+);?/g, 1980 trimRightRegExp = /\s+$/, 1981 urlColorRegExp = /rgb/, 1982 undef, i, encodingLookup = {}, encodingItems; 1983 1984 settings = settings || {}; 1985 1986 encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' '); 1987 for (i = 0; i < encodingItems.length; i++) { 1988 encodingLookup[encodingItems[i]] = '\uFEFF' + i; 1989 encodingLookup['\uFEFF' + i] = encodingItems[i]; 1990 } 1991 1992 function toHex(match, r, g, b) { 1993 function hex(val) { 1994 val = parseInt(val).toString(16); 1995 1996 return val.length > 1 ? val : '0' + val; // 0 -> 00 1997 }; 1998 1999 return '#' + hex(r) + hex(g) + hex(b); 2000 }; 2001 2002 return { 2003 toHex : function(color) { 2004 return color.replace(rgbRegExp, toHex); 2005 }, 2006 2007 parse : function(css) { 2008 var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this; 2009 2010 function compress(prefix, suffix) { 2011 var top, right, bottom, left; 2012 2013 // Get values and check it it needs compressing 2014 top = styles[prefix + '-top' + suffix]; 2015 if (!top) 2016 return; 2017 2018 right = styles[prefix + '-right' + suffix]; 2019 if (top != right) 2020 return; 2021 2022 bottom = styles[prefix + '-bottom' + suffix]; 2023 if (right != bottom) 2024 return; 2025 2026 left = styles[prefix + '-left' + suffix]; 2027 if (bottom != left) 2028 return; 2029 2030 // Compress 2031 styles[prefix + suffix] = left; 2032 delete styles[prefix + '-top' + suffix]; 2033 delete styles[prefix + '-right' + suffix]; 2034 delete styles[prefix + '-bottom' + suffix]; 2035 delete styles[prefix + '-left' + suffix]; 2036 }; 2037 2038 function canCompress(key) { 2039 var value = styles[key], i; 2040 2041 if (!value || value.indexOf(' ') < 0) 2042 return; 2043 2044 value = value.split(' '); 2045 i = value.length; 2046 while (i--) { 2047 if (value[i] !== value[0]) 2048 return false; 2049 } 2050 2051 styles[key] = value[0]; 2052 2053 return true; 2054 }; 2055 2056 function compress2(target, a, b, c) { 2057 if (!canCompress(a)) 2058 return; 2059 2060 if (!canCompress(b)) 2061 return; 2062 2063 if (!canCompress(c)) 2064 return; 2065 2066 // Compress 2067 styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c]; 2068 delete styles[a]; 2069 delete styles[b]; 2070 delete styles[c]; 2071 }; 2072 2073 // Encodes the specified string by replacing all \" \' ; : with _<num> 2074 function encode(str) { 2075 isEncoded = true; 2076 2077 return encodingLookup[str]; 2078 }; 2079 2080 // Decodes the specified string by replacing all _<num> with it's original value \" \' etc 2081 // It will also decode the \" \' if keep_slashes is set to fale or omitted 2082 function decode(str, keep_slashes) { 2083 if (isEncoded) { 2084 str = str.replace(/\uFEFF[0-9]/g, function(str) { 2085 return encodingLookup[str]; 2086 }); 2087 } 2088 2089 if (!keep_slashes) 2090 str = str.replace(/\\([\'\";:])/g, "$1"); 2091 2092 return str; 2093 }; 2094 2095 function processUrl(match, url, url2, url3, str, str2) { 2096 str = str || str2; 2097 2098 if (str) { 2099 str = decode(str); 2100 2101 // Force strings into single quote format 2102 return "'" + str.replace(/\'/g, "\\'") + "'"; 2103 } 2104 2105 url = decode(url || url2 || url3); 2106 2107 // Convert the URL to relative/absolute depending on config 2108 if (urlConverter) 2109 url = urlConverter.call(urlConverterScope, url, 'style'); 2110 2111 // Output new URL format 2112 return "url('" + url.replace(/\'/g, "\\'") + "')"; 2113 }; 2114 2115 if (css) { 2116 // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing 2117 css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) { 2118 return str.replace(/[;:]/g, encode); 2119 }); 2120 2121 // Parse styles 2122 while (matches = styleRegExp.exec(css)) { 2123 name = matches[1].replace(trimRightRegExp, '').toLowerCase(); 2124 value = matches[2].replace(trimRightRegExp, ''); 2125 2126 if (name && value.length > 0) { 2127 // Opera will produce 700 instead of bold in their style values 2128 if (name === 'font-weight' && value === '700') 2129 value = 'bold'; 2130 else if (name === 'color' || name === 'background-color') // Lowercase colors like RED 2131 value = value.toLowerCase(); 2132 2133 // Convert RGB colors to HEX 2134 value = value.replace(rgbRegExp, toHex); 2135 2136 // Convert URLs and force them into url('value') format 2137 value = value.replace(urlOrStrRegExp, processUrl); 2138 styles[name] = isEncoded ? decode(value, true) : value; 2139 } 2140 2141 styleRegExp.lastIndex = matches.index + matches[0].length; 2142 } 2143 2144 // Compress the styles to reduce it's size for example IE will expand styles 2145 compress("border", ""); 2146 compress("border", "-width"); 2147 compress("border", "-color"); 2148 compress("border", "-style"); 2149 compress("padding", ""); 2150 compress("margin", ""); 2151 compress2('border', 'border-width', 'border-style', 'border-color'); 2152 2153 // Remove pointless border, IE produces these 2154 if (styles.border === 'medium none') 2155 delete styles.border; 2156 } 2157 2158 return styles; 2159 }, 2160 2161 serialize : function(styles, element_name) { 2162 var css = '', name, value; 2163 2164 function serializeStyles(name) { 2165 var styleList, i, l, value; 2166 2167 styleList = schema.styles[name]; 2168 if (styleList) { 2169 for (i = 0, l = styleList.length; i < l; i++) { 2170 name = styleList[i]; 2171 value = styles[name]; 2172 2173 if (value !== undef && value.length > 0) 2174 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; 2175 } 2176 } 2177 }; 2178 2179 // Serialize styles according to schema 2180 if (element_name && schema && schema.styles) { 2181 // Serialize global styles and element specific styles 2182 serializeStyles('*'); 2183 serializeStyles(element_name); 2184 } else { 2185 // Output the styles in the order they are inside the object 2186 for (name in styles) { 2187 value = styles[name]; 2188 2189 if (value !== undef && value.length > 0) 2190 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; 2191 } 2192 } 2193 2194 return css; 2195 } 2196 }; 2197 }; 2198 2199 (function(tinymce) { 2200 var mapCache = {}, makeMap = tinymce.makeMap, each = tinymce.each; 2201 2202 function split(str, delim) { 2203 return str.split(delim || ','); 2204 }; 2205 2206 function unpack(lookup, data) { 2207 var key, elements = {}; 2208 2209 function replace(value) { 2210 return value.replace(/[A-Z]+/g, function(key) { 2211 return replace(lookup[key]); 2212 }); 2213 }; 2214 2215 // Unpack lookup 2216 for (key in lookup) { 2217 if (lookup.hasOwnProperty(key)) 2218 lookup[key] = replace(lookup[key]); 2219 } 2220 2221 // Unpack and parse data into object map 2222 replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) { 2223 attributes = split(attributes, '|'); 2224 2225 elements[name] = { 2226 attributes : makeMap(attributes), 2227 attributesOrder : attributes, 2228 children : makeMap(children, '|', {'#comment' : {}}) 2229 } 2230 }); 2231 2232 return elements; 2233 }; 2234 2235 function getHTML5() { 2236 var html5 = mapCache.html5; 2237 2238 if (!html5) { 2239 html5 = mapCache.html5 = unpack({ 2240 A : 'id|accesskey|class|dir|draggable|item|hidden|itemprop|role|spellcheck|style|subject|title|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup', 2241 B : '#|a|abbr|area|audio|b|bdo|br|button|canvas|cite|code|command|datalist|del|dfn|em|embed|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|meta|' + 2242 'meter|noscript|object|output|progress|q|ruby|samp|script|select|small|span|strong|sub|sup|svg|textarea|time|var|video|wbr', 2243 C : '#|a|abbr|area|address|article|aside|audio|b|bdo|blockquote|br|button|canvas|cite|code|command|datalist|del|details|dfn|dialog|div|dl|em|embed|fieldset|' + 2244 'figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|menu|meta|meter|nav|noscript|ol|object|output|' + 2245 'p|pre|progress|q|ruby|samp|script|section|select|small|span|strong|style|sub|sup|svg|table|textarea|time|ul|var|video' 2246 }, 'html[A|manifest][body|head]' + 2247 'head[A][base|command|link|meta|noscript|script|style|title]' + 2248 'title[A][#]' + 2249 'base[A|href|target][]' + 2250 'link[A|href|rel|media|type|sizes][]' + 2251 'meta[A|http-equiv|name|content|charset][]' + 2252 'style[A|type|media|scoped][#]' + 2253 'script[A|charset|type|src|defer|async][#]' + 2254 'noscript[A][C]' + 2255 'body[A][C]' + 2256 'section[A][C]' + 2257 'nav[A][C]' + 2258 'article[A][C]' + 2259 'aside[A][C]' + 2260 'h1[A][B]' + 2261 'h2[A][B]' + 2262 'h3[A][B]' + 2263 'h4[A][B]' + 2264 'h5[A][B]' + 2265 'h6[A][B]' + 2266 'hgroup[A][h1|h2|h3|h4|h5|h6]' + 2267 'header[A][C]' + 2268 'footer[A][C]' + 2269 'address[A][C]' + 2270 'p[A][B]' + 2271 'br[A][]' + 2272 'pre[A][B]' + 2273 'dialog[A][dd|dt]' + 2274 'blockquote[A|cite][C]' + 2275 'ol[A|start|reversed][li]' + 2276 'ul[A][li]' + 2277 'li[A|value][C]' + 2278 'dl[A][dd|dt]' + 2279 'dt[A][B]' + 2280 'dd[A][C]' + 2281 'a[A|href|target|ping|rel|media|type][B]' + 2282 'em[A][B]' + 2283 'strong[A][B]' + 2284 'small[A][B]' + 2285 'cite[A][B]' + 2286 'q[A|cite][B]' + 2287 'dfn[A][B]' + 2288 'abbr[A][B]' + 2289 'code[A][B]' + 2290 'var[A][B]' + 2291 'samp[A][B]' + 2292 'kbd[A][B]' + 2293 'sub[A][B]' + 2294 'sup[A][B]' + 2295 'i[A][B]' + 2296 'b[A][B]' + 2297 'mark[A][B]' + 2298 'progress[A|value|max][B]' + 2299 'meter[A|value|min|max|low|high|optimum][B]' + 2300 'time[A|datetime][B]' + 2301 'ruby[A][B|rt|rp]' + 2302 'rt[A][B]' + 2303 'rp[A][B]' + 2304 'bdo[A][B]' + 2305 'span[A][B]' + 2306 'ins[A|cite|datetime][B]' + 2307 'del[A|cite|datetime][B]' + 2308 'figure[A][C|legend|figcaption]' + 2309 'figcaption[A][C]' + 2310 'img[A|alt|src|height|width|usemap|ismap][]' + 2311 'iframe[A|name|src|height|width|sandbox|seamless][]' + 2312 'embed[A|src|height|width|type][]' + 2313 'object[A|data|type|height|width|usemap|name|form|classid][param]' + 2314 'param[A|name|value][]' + 2315 'details[A|open][C|legend]' + 2316 'command[A|type|label|icon|disabled|checked|radiogroup][]' + 2317 'menu[A|type|label][C|li]' + 2318 'legend[A][C|B]' + 2319 'div[A][C]' + 2320 'source[A|src|type|media][]' + 2321 'audio[A|src|autobuffer|autoplay|loop|controls][source]' + 2322 'video[A|src|autobuffer|autoplay|loop|controls|width|height|poster][source]' + 2323 'hr[A][]' + 2324 'form[A|accept-charset|action|autocomplete|enctype|method|name|novalidate|target][C]' + 2325 'fieldset[A|disabled|form|name][C|legend]' + 2326 'label[A|form|for][B]' + 2327 'input[A|type|accept|alt|autocomplete|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|' + 2328 'multiple|pattern|placeholder|readonly|required|size|src|step|width|files|value|name][]' + 2329 'button[A|autofocus|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|name|value|type][B]' + 2330 'select[A|autofocus|disabled|form|multiple|name|size][option|optgroup]' + 2331 'datalist[A][B|option]' + 2332 'optgroup[A|disabled|label][option]' + 2333 'option[A|disabled|selected|label|value][]' + 2334 'textarea[A|autofocus|disabled|form|maxlength|name|placeholder|readonly|required|rows|cols|wrap][]' + 2335 'keygen[A|autofocus|challenge|disabled|form|keytype|name][]' + 2336 'output[A|for|form|name][B]' + 2337 'canvas[A|width|height][]' + 2338 'map[A|name][B|C]' + 2339 'area[A|shape|coords|href|alt|target|media|rel|ping|type][]' + 2340 'mathml[A][]' + 2341 'svg[A][]' + 2342 'table[A|border][caption|colgroup|thead|tfoot|tbody|tr]' + 2343 'caption[A][C]' + 2344 'colgroup[A|span][col]' + 2345 'col[A|span][]' + 2346 'thead[A][tr]' + 2347 'tfoot[A][tr]' + 2348 'tbody[A][tr]' + 2349 'tr[A][th|td]' + 2350 'th[A|headers|rowspan|colspan|scope][B]' + 2351 'td[A|headers|rowspan|colspan][C]' + 2352 'wbr[A][]' 2353 ); 2354 } 2355 2356 return html5; 2357 }; 2358 2359 function getHTML4() { 2360 var html4 = mapCache.html4; 2361 2362 if (!html4) { 2363 // This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size 2364 html4 = mapCache.html4 = unpack({ 2365 Z : 'H|K|N|O|P', 2366 Y : 'X|form|R|Q', 2367 ZG : 'E|span|width|align|char|charoff|valign', 2368 X : 'p|T|div|U|W|isindex|fieldset|table', 2369 ZF : 'E|align|char|charoff|valign', 2370 W : 'pre|hr|blockquote|address|center|noframes', 2371 ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height', 2372 ZD : '[E][S]', 2373 U : 'ul|ol|dl|menu|dir', 2374 ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q', 2375 T : 'h1|h2|h3|h4|h5|h6', 2376 ZB : 'X|S|Q', 2377 S : 'R|P', 2378 ZA : 'a|G|J|M|O|P', 2379 R : 'a|H|K|N|O', 2380 Q : 'noscript|P', 2381 P : 'ins|del|script', 2382 O : 'input|select|textarea|label|button', 2383 N : 'M|L', 2384 M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym', 2385 L : 'sub|sup', 2386 K : 'J|I', 2387 J : 'tt|i|b|u|s|strike', 2388 I : 'big|small|font|basefont', 2389 H : 'G|F', 2390 G : 'br|span|bdo', 2391 F : 'object|applet|img|map|iframe', 2392 E : 'A|B|C', 2393 D : 'accesskey|tabindex|onfocus|onblur', 2394 C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup', 2395 B : 'lang|xml:lang|dir', 2396 A : 'id|class|style|title' 2397 }, 'script[id|charset|type|language|src|defer|xml:space][]' + 2398 'style[B|id|type|media|title|xml:space][]' + 2399 'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + 2400 'param[id|name|value|valuetype|type][]' + 2401 'p[E|align][#|S]' + 2402 'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + 2403 'br[A|clear][]' + 2404 'span[E][#|S]' + 2405 'bdo[A|C|B][#|S]' + 2406 'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + 2407 'h1[E|align][#|S]' + 2408 'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + 2409 'map[B|C|A|name][X|form|Q|area]' + 2410 'h2[E|align][#|S]' + 2411 'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + 2412 'h3[E|align][#|S]' + 2413 'tt[E][#|S]' + 2414 'i[E][#|S]' + 2415 'b[E][#|S]' + 2416 'u[E][#|S]' + 2417 's[E][#|S]' + 2418 'strike[E][#|S]' + 2419 'big[E][#|S]' + 2420 'small[E][#|S]' + 2421 'font[A|B|size|color|face][#|S]' + 2422 'basefont[id|size|color|face][]' + 2423 'em[E][#|S]' + 2424 'strong[E][#|S]' + 2425 'dfn[E][#|S]' + 2426 'code[E][#|S]' + 2427 'q[E|cite][#|S]' + 2428 'samp[E][#|S]' + 2429 'kbd[E][#|S]' + 2430 'var[E][#|S]' + 2431 'cite[E][#|S]' + 2432 'abbr[E][#|S]' + 2433 'acronym[E][#|S]' + 2434 'sub[E][#|S]' + 2435 'sup[E][#|S]' + 2436 'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + 2437 'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + 2438 'optgroup[E|disabled|label][option]' + 2439 'option[E|selected|disabled|label|value][]' + 2440 'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + 2441 'label[E|for|accesskey|onfocus|onblur][#|S]' + 2442 'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + 2443 'h4[E|align][#|S]' + 2444 'ins[E|cite|datetime][#|Y]' + 2445 'h5[E|align][#|S]' + 2446 'del[E|cite|datetime][#|Y]' + 2447 'h6[E|align][#|S]' + 2448 'div[E|align][#|Y]' + 2449 'ul[E|type|compact][li]' + 2450 'li[E|type|value][#|Y]' + 2451 'ol[E|type|compact|start][li]' + 2452 'dl[E|compact][dt|dd]' + 2453 'dt[E][#|S]' + 2454 'dd[E][#|Y]' + 2455 'menu[E|compact][li]' + 2456 'dir[E|compact][li]' + 2457 'pre[E|width|xml:space][#|ZA]' + 2458 'hr[E|align|noshade|size|width][]' + 2459 'blockquote[E|cite][#|Y]' + 2460 'address[E][#|S|p]' + 2461 'center[E][#|Y]' + 2462 'noframes[E][#|Y]' + 2463 'isindex[A|B|prompt][]' + 2464 'fieldset[E][#|legend|Y]' + 2465 'legend[E|accesskey|align][#|S]' + 2466 'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + 2467 'caption[E|align][#|S]' + 2468 'col[ZG][]' + 2469 'colgroup[ZG][col]' + 2470 'thead[ZF][tr]' + 2471 'tr[ZF|bgcolor][th|td]' + 2472 'th[E|ZE][#|Y]' + 2473 'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + 2474 'noscript[E][#|Y]' + 2475 'td[E|ZE][#|Y]' + 2476 'tfoot[ZF][tr]' + 2477 'tbody[ZF][tr]' + 2478 'area[E|D|shape|coords|href|nohref|alt|target][]' + 2479 'base[id|href|target][]' + 2480 'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]' 2481 ); 2482 } 2483 2484 return html4; 2485 }; 2486 2487 tinymce.html.Schema = function(settings) { 2488 var self = this, elements = {}, children = {}, patternElements = [], validStyles, schemaItems; 2489 var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, blockElementsMap, nonEmptyElementsMap, customElementsMap = {}; 2490 2491 // Creates an lookup table map object for the specified option or the default value 2492 function createLookupTable(option, default_value, extend) { 2493 var value = settings[option]; 2494 2495 if (!value) { 2496 // Get cached default map or make it if needed 2497 value = mapCache[option]; 2498 2499 if (!value) { 2500 value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' ')); 2501 value = tinymce.extend(value, extend); 2502 2503 mapCache[option] = value; 2504 } 2505 } else { 2506 // Create custom map 2507 value = makeMap(value, ',', makeMap(value.toUpperCase(), ' ')); 2508 } 2509 2510 return value; 2511 }; 2512 2513 settings = settings || {}; 2514 schemaItems = settings.schema == "html5" ? getHTML5() : getHTML4(); 2515 2516 // Allow all elements and attributes if verify_html is set to false 2517 if (settings.verify_html === false) 2518 settings.valid_elements = '*[*]'; 2519 2520 // Build styles list 2521 if (settings.valid_styles) { 2522 validStyles = {}; 2523 2524 // Convert styles into a rule list 2525 each(settings.valid_styles, function(value, key) { 2526 validStyles[key] = tinymce.explode(value); 2527 }); 2528 } 2529 2530 // Setup map objects 2531 whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script style textarea'); 2532 selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr'); 2533 shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link meta param embed source wbr'); 2534 boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls'); 2535 nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object', shortEndedElementsMap); 2536 blockElementsMap = createLookupTable('block_elements', 'h1 h2 h3 h4 h5 h6 hr p div address pre form table tbody thead tfoot ' + 2537 'th tr td li ol ul caption blockquote center dl dt dd dir fieldset ' + 2538 'noscript menu isindex samp header footer article section hgroup aside nav figure option datalist select optgroup'); 2539 2540 // Converts a wildcard expression string to a regexp for example *a will become /.*a/. 2541 function patternToRegExp(str) { 2542 return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$'); 2543 }; 2544 2545 // Parses the specified valid_elements string and adds to the current rules 2546 // This function is a bit hard to read since it's heavily optimized for speed 2547 function addValidElements(valid_elements) { 2548 var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder, 2549 prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value, 2550 elementRuleRegExp = /^([#+\-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/, 2551 attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/, 2552 hasPatternsRegExp = /[*?+]/; 2553 2554 if (valid_elements) { 2555 // Split valid elements into an array with rules 2556 valid_elements = split(valid_elements); 2557 2558 if (elements['@']) { 2559 globalAttributes = elements['@'].attributes; 2560 globalAttributesOrder = elements['@'].attributesOrder; 2561 } 2562 2563 // Loop all rules 2564 for (ei = 0, el = valid_elements.length; ei < el; ei++) { 2565 // Parse element rule 2566 matches = elementRuleRegExp.exec(valid_elements[ei]); 2567 if (matches) { 2568 // Setup local names for matches 2569 prefix = matches[1]; 2570 elementName = matches[2]; 2571 outputName = matches[3]; 2572 attrData = matches[4]; 2573 2574 // Create new attributes and attributesOrder 2575 attributes = {}; 2576 attributesOrder = []; 2577 2578 // Create the new element 2579 element = { 2580 attributes : attributes, 2581 attributesOrder : attributesOrder 2582 }; 2583 2584 // Padd empty elements prefix 2585 if (prefix === '#') 2586 element.paddEmpty = true; 2587 2588 // Remove empty elements prefix 2589 if (prefix === '-') 2590 element.removeEmpty = true; 2591 2592 // Copy attributes from global rule into current rule 2593 if (globalAttributes) { 2594 for (key in globalAttributes) 2595 attributes[key] = globalAttributes[key]; 2596 2597 attributesOrder.push.apply(attributesOrder, globalAttributesOrder); 2598 } 2599 2600 // Attributes defined 2601 if (attrData) { 2602 attrData = split(attrData, '|'); 2603 for (ai = 0, al = attrData.length; ai < al; ai++) { 2604 matches = attrRuleRegExp.exec(attrData[ai]); 2605 if (matches) { 2606 attr = {}; 2607 attrType = matches[1]; 2608 attrName = matches[2].replace(/::/g, ':'); 2609 prefix = matches[3]; 2610 value = matches[4]; 2611 2612 // Required 2613 if (attrType === '!') { 2614 element.attributesRequired = element.attributesRequired || []; 2615 element.attributesRequired.push(attrName); 2616 attr.required = true; 2617 } 2618 2619 // Denied from global 2620 if (attrType === '-') { 2621 delete attributes[attrName]; 2622 attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1); 2623 continue; 2624 } 2625 2626 // Default value 2627 if (prefix) { 2628 // Default value 2629 if (prefix === '=') { 2630 element.attributesDefault = element.attributesDefault || []; 2631 element.attributesDefault.push({name: attrName, value: value}); 2632 attr.defaultValue = value; 2633 } 2634 2635 // Forced value 2636 if (prefix === ':') { 2637 element.attributesForced = element.attributesForced || []; 2638 element.attributesForced.push({name: attrName, value: value}); 2639 attr.forcedValue = value; 2640 } 2641 2642 // Required values 2643 if (prefix === '<') 2644 attr.validValues = makeMap(value, '?'); 2645 } 2646 2647 // Check for attribute patterns 2648 if (hasPatternsRegExp.test(attrName)) { 2649 element.attributePatterns = element.attributePatterns || []; 2650 attr.pattern = patternToRegExp(attrName); 2651 element.attributePatterns.push(attr); 2652 } else { 2653 // Add attribute to order list if it doesn't already exist 2654 if (!attributes[attrName]) 2655 attributesOrder.push(attrName); 2656 2657 attributes[attrName] = attr; 2658 } 2659 } 2660 } 2661 } 2662 2663 // Global rule, store away these for later usage 2664 if (!globalAttributes && elementName == '@') { 2665 globalAttributes = attributes; 2666 globalAttributesOrder = attributesOrder; 2667 } 2668 2669 // Handle substitute elements such as b/strong 2670 if (outputName) { 2671 element.outputName = elementName; 2672 elements[outputName] = element; 2673 } 2674 2675 // Add pattern or exact element 2676 if (hasPatternsRegExp.test(elementName)) { 2677 element.pattern = patternToRegExp(elementName); 2678 patternElements.push(element); 2679 } else 2680 elements[elementName] = element; 2681 } 2682 } 2683 } 2684 }; 2685 2686 function setValidElements(valid_elements) { 2687 elements = {}; 2688 patternElements = []; 2689 2690 addValidElements(valid_elements); 2691 2692 each(schemaItems, function(element, name) { 2693 children[name] = element.children; 2694 }); 2695 }; 2696 2697 // Adds custom non HTML elements to the schema 2698 function addCustomElements(custom_elements) { 2699 var customElementRegExp = /^(~)?(.+)$/; 2700 2701 if (custom_elements) { 2702 each(split(custom_elements), function(rule) { 2703 var matches = customElementRegExp.exec(rule), 2704 inline = matches[1] === '~', 2705 cloneName = inline ? 'span' : 'div', 2706 name = matches[2]; 2707 2708 children[name] = children[cloneName]; 2709 customElementsMap[name] = cloneName; 2710 2711 // If it's not marked as inline then add it to valid block elements 2712 if (!inline) 2713 blockElementsMap[name] = {}; 2714 2715 // Add custom elements at span/div positions 2716 each(children, function(element, child) { 2717 if (element[cloneName]) 2718 element[name] = element[cloneName]; 2719 }); 2720 }); 2721 } 2722 }; 2723 2724 // Adds valid children to the schema object 2725 function addValidChildren(valid_children) { 2726 var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/; 2727 2728 if (valid_children) { 2729 each(split(valid_children), function(rule) { 2730 var matches = childRuleRegExp.exec(rule), parent, prefix; 2731 2732 if (matches) { 2733 prefix = matches[1]; 2734 2735 // Add/remove items from default 2736 if (prefix) 2737 parent = children[matches[2]]; 2738 else 2739 parent = children[matches[2]] = {'#comment' : {}}; 2740 2741 parent = children[matches[2]]; 2742 2743 each(split(matches[3], '|'), function(child) { 2744 if (prefix === '-') 2745 delete parent[child]; 2746 else 2747 parent[child] = {}; 2748 }); 2749 } 2750 }); 2751 } 2752 }; 2753 2754 function getElementRule(name) { 2755 var element = elements[name], i; 2756 2757 // Exact match found 2758 if (element) 2759 return element; 2760 2761 // No exact match then try the patterns 2762 i = patternElements.length; 2763 while (i--) { 2764 element = patternElements[i]; 2765 2766 if (element.pattern.test(name)) 2767 return element; 2768 } 2769 }; 2770 2771 if (!settings.valid_elements) { 2772 // No valid elements defined then clone the elements from the schema spec 2773 each(schemaItems, function(element, name) { 2774 elements[name] = { 2775 attributes : element.attributes, 2776 attributesOrder : element.attributesOrder 2777 }; 2778 2779 children[name] = element.children; 2780 }); 2781 2782 // Switch these on HTML4 2783 if (settings.schema != "html5") { 2784 each(split('strong/b,em/i'), function(item) { 2785 item = split(item, '/'); 2786 elements[item[1]].outputName = item[0]; 2787 }); 2788 } 2789 2790 // Add default alt attribute for images 2791 elements.img.attributesDefault = [{name: 'alt', value: ''}]; 2792 2793 // Remove these if they are empty by default 2794 each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr,strong,em,b,i'), function(name) { 2795 if (elements[name]) { 2796 elements[name].removeEmpty = true; 2797 } 2798 }); 2799 2800 // Padd these by default 2801 each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) { 2802 elements[name].paddEmpty = true; 2803 }); 2804 } else 2805 setValidElements(settings.valid_elements); 2806 2807 addCustomElements(settings.custom_elements); 2808 addValidChildren(settings.valid_children); 2809 addValidElements(settings.extended_valid_elements); 2810 2811 // Todo: Remove this when we fix list handling to be valid 2812 addValidChildren('+ol[ul|ol],+ul[ul|ol]'); 2813 2814 // Delete invalid elements 2815 if (settings.invalid_elements) { 2816 tinymce.each(tinymce.explode(settings.invalid_elements), function(item) { 2817 if (elements[item]) 2818 delete elements[item]; 2819 }); 2820 } 2821 2822 // If the user didn't allow span only allow internal spans 2823 if (!getElementRule('span')) 2824 addValidElements('span[!data-mce-type|*]'); 2825 2826 self.children = children; 2827 2828 self.styles = validStyles; 2829 2830 self.getBoolAttrs = function() { 2831 return boolAttrMap; 2832 }; 2833 2834 self.getBlockElements = function() { 2835 return blockElementsMap; 2836 }; 2837 2838 self.getShortEndedElements = function() { 2839 return shortEndedElementsMap; 2840 }; 2841 2842 self.getSelfClosingElements = function() { 2843 return selfClosingElementsMap; 2844 }; 2845 2846 self.getNonEmptyElements = function() { 2847 return nonEmptyElementsMap; 2848 }; 2849 2850 self.getWhiteSpaceElements = function() { 2851 return whiteSpaceElementsMap; 2852 }; 2853 2854 self.isValidChild = function(name, child) { 2855 var parent = children[name]; 2856 2857 return !!(parent && parent[child]); 2858 }; 2859 2860 self.isValid = function(name, attr) { 2861 var attrPatterns, i, rule = getElementRule(name); 2862 2863 // Check if it's a valid element 2864 if (rule) { 2865 if (attr) { 2866 // Check if attribute name exists 2867 if (rule.attributes[attr]) { 2868 return true; 2869 } 2870 2871 // Check if attribute matches a regexp pattern 2872 attrPatterns = rule.attributePatterns; 2873 if (attrPatterns) { 2874 i = attrPatterns.length; 2875 while (i--) { 2876 if (attrPatterns[i].pattern.test(name)) { 2877 return true; 2878 } 2879 } 2880 } 2881 } else { 2882 return true; 2883 } 2884 } 2885 2886 // No match 2887 return false; 2888 }; 2889 2890 self.getElementRule = getElementRule; 2891 2892 self.getCustomElements = function() { 2893 return customElementsMap; 2894 }; 2895 2896 self.addValidElements = addValidElements; 2897 2898 self.setValidElements = setValidElements; 2899 2900 self.addCustomElements = addCustomElements; 2901 2902 self.addValidChildren = addValidChildren; 2903 }; 2904 })(tinymce); 2905 2906 (function(tinymce) { 2907 tinymce.html.SaxParser = function(settings, schema) { 2908 var self = this, noop = function() {}; 2909 2910 settings = settings || {}; 2911 self.schema = schema = schema || new tinymce.html.Schema(); 2912 2913 if (settings.fix_self_closing !== false) 2914 settings.fix_self_closing = true; 2915 2916 // Add handler functions from settings and setup default handlers 2917 tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) { 2918 if (name) 2919 self[name] = settings[name] || noop; 2920 }); 2921 2922 self.parse = function(html) { 2923 var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements, 2924 shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp, 2925 validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing, 2926 tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE; 2927 2928 function processEndTag(name) { 2929 var pos, i; 2930 2931 // Find position of parent of the same type 2932 pos = stack.length; 2933 while (pos--) { 2934 if (stack[pos].name === name) 2935 break; 2936 } 2937 2938 // Found parent 2939 if (pos >= 0) { 2940 // Close all the open elements 2941 for (i = stack.length - 1; i >= pos; i--) { 2942 name = stack[i]; 2943 2944 if (name.valid) 2945 self.end(name.name); 2946 } 2947 2948 // Remove the open elements from the stack 2949 stack.length = pos; 2950 } 2951 }; 2952 2953 function parseAttribute(match, name, value, val2, val3) { 2954 var attrRule, i; 2955 2956 name = name.toLowerCase(); 2957 value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute 2958 2959 // Validate name and value 2960 if (validate && !isInternalElement && name.indexOf('data-') !== 0) { 2961 attrRule = validAttributesMap[name]; 2962 2963 // Find rule by pattern matching 2964 if (!attrRule && validAttributePatterns) { 2965 i = validAttributePatterns.length; 2966 while (i--) { 2967 attrRule = validAttributePatterns[i]; 2968 if (attrRule.pattern.test(name)) 2969 break; 2970 } 2971 2972 // No rule matched 2973 if (i === -1) 2974 attrRule = null; 2975 } 2976 2977 // No attribute rule found 2978 if (!attrRule) 2979 return; 2980 2981 // Validate value 2982 if (attrRule.validValues && !(value in attrRule.validValues)) 2983 return; 2984 } 2985 2986 // Add attribute to list and map 2987 attrList.map[name] = value; 2988 attrList.push({ 2989 name: name, 2990 value: value 2991 }); 2992 }; 2993 2994 // Precompile RegExps and map objects 2995 tokenRegExp = new RegExp('<(?:' + 2996 '(?:!--([\\w\\W]*?)-->)|' + // Comment 2997 '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA 2998 '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE 2999 '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI 3000 '(?:\\/([^>]+)>)|' + // End element 3001 '(?:([A-Za-z0-9\\-\\:]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element 3002 ')', 'g'); 3003 3004 attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g; 3005 specialElements = { 3006 'script' : /<\/script[^>]*>/gi, 3007 'style' : /<\/style[^>]*>/gi, 3008 'noscript' : /<\/noscript[^>]*>/gi 3009 }; 3010 3011 // Setup lookup tables for empty elements and boolean attributes 3012 shortEndedElements = schema.getShortEndedElements(); 3013 selfClosing = settings.self_closing_elements || schema.getSelfClosingElements(); 3014 fillAttrsMap = schema.getBoolAttrs(); 3015 validate = settings.validate; 3016 removeInternalElements = settings.remove_internals; 3017 fixSelfClosing = settings.fix_self_closing; 3018 isIE = tinymce.isIE; 3019 invalidPrefixRegExp = /^:/; 3020 3021 while (matches = tokenRegExp.exec(html)) { 3022 // Text 3023 if (index < matches.index) 3024 self.text(decode(html.substr(index, matches.index - index))); 3025 3026 if (value = matches[6]) { // End element 3027 value = value.toLowerCase(); 3028 3029 // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements 3030 if (isIE && invalidPrefixRegExp.test(value)) 3031 value = value.substr(1); 3032 3033 processEndTag(value); 3034 } else if (value = matches[7]) { // Start element 3035 value = value.toLowerCase(); 3036 3037 // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements 3038 if (isIE && invalidPrefixRegExp.test(value)) 3039 value = value.substr(1); 3040 3041 isShortEnded = value in shortEndedElements; 3042 3043 // Is self closing tag for example an <li> after an open <li> 3044 if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value) 3045 processEndTag(value); 3046 3047 // Validate element 3048 if (!validate || (elementRule = schema.getElementRule(value))) { 3049 isValidElement = true; 3050 3051 // Grab attributes map and patters when validation is enabled 3052 if (validate) { 3053 validAttributesMap = elementRule.attributes; 3054 validAttributePatterns = elementRule.attributePatterns; 3055 } 3056 3057 // Parse attributes 3058 if (attribsValue = matches[8]) { 3059 isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element 3060 3061 // If the element has internal attributes then remove it if we are told to do so 3062 if (isInternalElement && removeInternalElements) 3063 isValidElement = false; 3064 3065 attrList = []; 3066 attrList.map = {}; 3067 3068 attribsValue.replace(attrRegExp, parseAttribute); 3069 } else { 3070 attrList = []; 3071 attrList.map = {}; 3072 } 3073 3074 // Process attributes if validation is enabled 3075 if (validate && !isInternalElement) { 3076 attributesRequired = elementRule.attributesRequired; 3077 attributesDefault = elementRule.attributesDefault; 3078 attributesForced = elementRule.attributesForced; 3079 3080 // Handle forced attributes 3081 if (attributesForced) { 3082 i = attributesForced.length; 3083 while (i--) { 3084 attr = attributesForced[i]; 3085 name = attr.name; 3086 attrValue = attr.value; 3087 3088 if (attrValue === '{$uid}') 3089 attrValue = 'mce_' + idCount++; 3090 3091 attrList.map[name] = attrValue; 3092 attrList.push({name: name, value: attrValue}); 3093 } 3094 } 3095 3096 // Handle default attributes 3097 if (attributesDefault) { 3098 i = attributesDefault.length; 3099 while (i--) { 3100 attr = attributesDefault[i]; 3101 name = attr.name; 3102 3103 if (!(name in attrList.map)) { 3104 attrValue = attr.value; 3105 3106 if (attrValue === '{$uid}') 3107 attrValue = 'mce_' + idCount++; 3108 3109 attrList.map[name] = attrValue; 3110 attrList.push({name: name, value: attrValue}); 3111 } 3112 } 3113 } 3114 3115 // Handle required attributes 3116 if (attributesRequired) { 3117 i = attributesRequired.length; 3118 while (i--) { 3119 if (attributesRequired[i] in attrList.map) 3120 break; 3121 } 3122 3123 // None of the required attributes where found 3124 if (i === -1) 3125 isValidElement = false; 3126 } 3127 3128 // Invalidate element if it's marked as bogus 3129 if (attrList.map['data-mce-bogus']) 3130 isValidElement = false; 3131 } 3132 3133 if (isValidElement) 3134 self.start(value, attrList, isShortEnded); 3135 } else 3136 isValidElement = false; 3137 3138 // Treat script, noscript and style a bit different since they may include code that looks like elements 3139 if (endRegExp = specialElements[value]) { 3140 endRegExp.lastIndex = index = matches.index + matches[0].length; 3141 3142 if (matches = endRegExp.exec(html)) { 3143 if (isValidElement) 3144 text = html.substr(index, matches.index - index); 3145 3146 index = matches.index + matches[0].length; 3147 } else { 3148 text = html.substr(index); 3149 index = html.length; 3150 } 3151 3152 if (isValidElement && text.length > 0) 3153 self.text(text, true); 3154 3155 if (isValidElement) 3156 self.end(value); 3157 3158 tokenRegExp.lastIndex = index; 3159 continue; 3160 } 3161 3162 // Push value on to stack 3163 if (!isShortEnded) { 3164 if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1) 3165 stack.push({name: value, valid: isValidElement}); 3166 else if (isValidElement) 3167 self.end(value); 3168 } 3169 } else if (value = matches[1]) { // Comment 3170 self.comment(value); 3171 } else if (value = matches[2]) { // CDATA 3172 self.cdata(value); 3173 } else if (value = matches[3]) { // DOCTYPE 3174 self.doctype(value); 3175 } else if (value = matches[4]) { // PI 3176 self.pi(value, matches[5]); 3177 } 3178 3179 index = matches.index + matches[0].length; 3180 } 3181 3182 // Text 3183 if (index < html.length) 3184 self.text(decode(html.substr(index))); 3185 3186 // Close any open elements 3187 for (i = stack.length - 1; i >= 0; i--) { 3188 value = stack[i]; 3189 3190 if (value.valid) 3191 self.end(value.name); 3192 } 3193 }; 3194 } 3195 })(tinymce); 3196 3197 (function(tinymce) { 3198 var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = { 3199 '#text' : 3, 3200 '#comment' : 8, 3201 '#cdata' : 4, 3202 '#pi' : 7, 3203 '#doctype' : 10, 3204 '#document-fragment' : 11 3205 }; 3206 3207 // Walks the tree left/right 3208 function walk(node, root_node, prev) { 3209 var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next'; 3210 3211 // Walk into nodes if it has a start 3212 if (node[startName]) 3213 return node[startName]; 3214 3215 // Return the sibling if it has one 3216 if (node !== root_node) { 3217 sibling = node[siblingName]; 3218 3219 if (sibling) 3220 return sibling; 3221 3222 // Walk up the parents to look for siblings 3223 for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) { 3224 sibling = parent[siblingName]; 3225 3226 if (sibling) 3227 return sibling; 3228 } 3229 } 3230 }; 3231 3232 function Node(name, type) { 3233 this.name = name; 3234 this.type = type; 3235 3236 if (type === 1) { 3237 this.attributes = []; 3238 this.attributes.map = {}; 3239 } 3240 } 3241 3242 tinymce.extend(Node.prototype, { 3243 replace : function(node) { 3244 var self = this; 3245 3246 if (node.parent) 3247 node.remove(); 3248 3249 self.insert(node, self); 3250 self.remove(); 3251 3252 return self; 3253 }, 3254 3255 attr : function(name, value) { 3256 var self = this, attrs, i, undef; 3257 3258 if (typeof name !== "string") { 3259 for (i in name) 3260 self.attr(i, name[i]); 3261 3262 return self; 3263 } 3264 3265 if (attrs = self.attributes) { 3266 if (value !== undef) { 3267 // Remove attribute 3268 if (value === null) { 3269 if (name in attrs.map) { 3270 delete attrs.map[name]; 3271 3272 i = attrs.length; 3273 while (i--) { 3274 if (attrs[i].name === name) { 3275 attrs = attrs.splice(i, 1); 3276 return self; 3277 } 3278 } 3279 } 3280 3281 return self; 3282 } 3283 3284 // Set attribute 3285 if (name in attrs.map) { 3286 // Set attribute 3287 i = attrs.length; 3288 while (i--) { 3289 if (attrs[i].name === name) { 3290 attrs[i].value = value; 3291 break; 3292 } 3293 } 3294 } else 3295 attrs.push({name: name, value: value}); 3296 3297 attrs.map[name] = value; 3298 3299 return self; 3300 } else { 3301 return attrs.map[name]; 3302 } 3303 } 3304 }, 3305 3306 clone : function() { 3307 var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs; 3308 3309 // Clone element attributes 3310 if (selfAttrs = self.attributes) { 3311 cloneAttrs = []; 3312 cloneAttrs.map = {}; 3313 3314 for (i = 0, l = selfAttrs.length; i < l; i++) { 3315 selfAttr = selfAttrs[i]; 3316 3317 // Clone everything except id 3318 if (selfAttr.name !== 'id') { 3319 cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value}; 3320 cloneAttrs.map[selfAttr.name] = selfAttr.value; 3321 } 3322 } 3323 3324 clone.attributes = cloneAttrs; 3325 } 3326 3327 clone.value = self.value; 3328 clone.shortEnded = self.shortEnded; 3329 3330 return clone; 3331 }, 3332 3333 wrap : function(wrapper) { 3334 var self = this; 3335 3336 self.parent.insert(wrapper, self); 3337 wrapper.append(self); 3338 3339 return self; 3340 }, 3341 3342 unwrap : function() { 3343 var self = this, node, next; 3344 3345 for (node = self.firstChild; node; ) { 3346 next = node.next; 3347 self.insert(node, self, true); 3348 node = next; 3349 } 3350 3351 self.remove(); 3352 }, 3353 3354 remove : function() { 3355 var self = this, parent = self.parent, next = self.next, prev = self.prev; 3356 3357 if (parent) { 3358 if (parent.firstChild === self) { 3359 parent.firstChild = next; 3360 3361 if (next) 3362 next.prev = null; 3363 } else { 3364 prev.next = next; 3365 } 3366 3367 if (parent.lastChild === self) { 3368 parent.lastChild = prev; 3369 3370 if (prev) 3371 prev.next = null; 3372 } else { 3373 next.prev = prev; 3374 } 3375 3376 self.parent = self.next = self.prev = null; 3377 } 3378 3379 return self; 3380 }, 3381 3382 append : function(node) { 3383 var self = this, last; 3384 3385 if (node.parent) 3386 node.remove(); 3387 3388 last = self.lastChild; 3389 if (last) { 3390 last.next = node; 3391 node.prev = last; 3392 self.lastChild = node; 3393 } else 3394 self.lastChild = self.firstChild = node; 3395 3396 node.parent = self; 3397 3398 return node; 3399 }, 3400 3401 insert : function(node, ref_node, before) { 3402 var parent; 3403 3404 if (node.parent) 3405 node.remove(); 3406 3407 parent = ref_node.parent || this; 3408 3409 if (before) { 3410 if (ref_node === parent.firstChild) 3411 parent.firstChild = node; 3412 else 3413 ref_node.prev.next = node; 3414 3415 node.prev = ref_node.prev; 3416 node.next = ref_node; 3417 ref_node.prev = node; 3418 } else { 3419 if (ref_node === parent.lastChild) 3420 parent.lastChild = node; 3421 else 3422 ref_node.next.prev = node; 3423 3424 node.next = ref_node.next; 3425 node.prev = ref_node; 3426 ref_node.next = node; 3427 } 3428 3429 node.parent = parent; 3430 3431 return node; 3432 }, 3433 3434 getAll : function(name) { 3435 var self = this, node, collection = []; 3436 3437 for (node = self.firstChild; node; node = walk(node, self)) { 3438 if (node.name === name) 3439 collection.push(node); 3440 } 3441 3442 return collection; 3443 }, 3444 3445 empty : function() { 3446 var self = this, nodes, i, node; 3447 3448 // Remove all children 3449 if (self.firstChild) { 3450 nodes = []; 3451 3452 // Collect the children 3453 for (node = self.firstChild; node; node = walk(node, self)) 3454 nodes.push(node); 3455 3456 // Remove the children 3457 i = nodes.length; 3458 while (i--) { 3459 node = nodes[i]; 3460 node.parent = node.firstChild = node.lastChild = node.next = node.prev = null; 3461 } 3462 } 3463 3464 self.firstChild = self.lastChild = null; 3465 3466 return self; 3467 }, 3468 3469 isEmpty : function(elements) { 3470 var self = this, node = self.firstChild, i, name; 3471 3472 if (node) { 3473 do { 3474 if (node.type === 1) { 3475 // Ignore bogus elements 3476 if (node.attributes.map['data-mce-bogus']) 3477 continue; 3478 3479 // Keep empty elements like <img /> 3480 if (elements[node.name]) 3481 return false; 3482 3483 // Keep elements with data attributes or name attribute like <a name="1"></a> 3484 i = node.attributes.length; 3485 while (i--) { 3486 name = node.attributes[i].name; 3487 if (name === "name" || name.indexOf('data-') === 0) 3488 return false; 3489 } 3490 } 3491 3492 // Keep comments 3493 if (node.type === 8) 3494 return false; 3495 3496 // Keep non whitespace text nodes 3497 if ((node.type === 3 && !whiteSpaceRegExp.test(node.value))) 3498 return false; 3499 } while (node = walk(node, self)); 3500 } 3501 3502 return true; 3503 }, 3504 3505 walk : function(prev) { 3506 return walk(this, null, prev); 3507 } 3508 }); 3509 3510 tinymce.extend(Node, { 3511 create : function(name, attrs) { 3512 var node, attrName; 3513 3514 // Create node 3515 node = new Node(name, typeLookup[name] || 1); 3516 3517 // Add attributes if needed 3518 if (attrs) { 3519 for (attrName in attrs) 3520 node.attr(attrName, attrs[attrName]); 3521 } 3522 3523 return node; 3524 } 3525 }); 3526 3527 tinymce.html.Node = Node; 3528 })(tinymce); 3529 3530 (function(tinymce) { 3531 var Node = tinymce.html.Node; 3532 3533 tinymce.html.DomParser = function(settings, schema) { 3534 var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {}; 3535 3536 settings = settings || {}; 3537 settings.validate = "validate" in settings ? settings.validate : true; 3538 settings.root_name = settings.root_name || 'body'; 3539 self.schema = schema = schema || new tinymce.html.Schema(); 3540 3541 function fixInvalidChildren(nodes) { 3542 var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i, 3543 childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode; 3544 3545 nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table'); 3546 nonEmptyElements = schema.getNonEmptyElements(); 3547 3548 for (ni = 0; ni < nodes.length; ni++) { 3549 node = nodes[ni]; 3550 3551 // Already removed 3552 if (!node.parent) 3553 continue; 3554 3555 // Get list of all parent nodes until we find a valid parent to stick the child into 3556 parents = [node]; 3557 for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent) 3558 parents.push(parent); 3559 3560 // Found a suitable parent 3561 if (parent && parents.length > 1) { 3562 // Reverse the array since it makes looping easier 3563 parents.reverse(); 3564 3565 // Clone the related parent and insert that after the moved node 3566 newParent = currentNode = self.filterNode(parents[0].clone()); 3567 3568 // Start cloning and moving children on the left side of the target node 3569 for (i = 0; i < parents.length - 1; i++) { 3570 if (schema.isValidChild(currentNode.name, parents[i].name)) { 3571 tempNode = self.filterNode(parents[i].clone()); 3572 currentNode.append(tempNode); 3573 } else 3574 tempNode = currentNode; 3575 3576 for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) { 3577 nextNode = childNode.next; 3578 tempNode.append(childNode); 3579 childNode = nextNode; 3580 } 3581 3582 currentNode = tempNode; 3583 } 3584 3585 if (!newParent.isEmpty(nonEmptyElements)) { 3586 parent.insert(newParent, parents[0], true); 3587 parent.insert(node, newParent); 3588 } else { 3589 parent.insert(node, parents[0], true); 3590 } 3591 3592 // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p> 3593 parent = parents[0]; 3594 if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') { 3595 parent.empty().remove(); 3596 } 3597 } else if (node.parent) { 3598 // If it's an LI try to find a UL/OL for it or wrap it 3599 if (node.name === 'li') { 3600 sibling = node.prev; 3601 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { 3602 sibling.append(node); 3603 continue; 3604 } 3605 3606 sibling = node.next; 3607 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { 3608 sibling.insert(node, sibling.firstChild, true); 3609 continue; 3610 } 3611 3612 node.wrap(self.filterNode(new Node('ul', 1))); 3613 continue; 3614 } 3615 3616 // Try wrapping the element in a DIV 3617 if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) { 3618 node.wrap(self.filterNode(new Node('div', 1))); 3619 } else { 3620 // We failed wrapping it, then remove or unwrap it 3621 if (node.name === 'style' || node.name === 'script') 3622 node.empty().remove(); 3623 else 3624 node.unwrap(); 3625 } 3626 } 3627 } 3628 }; 3629 3630 self.filterNode = function(node) { 3631 var i, name, list; 3632 3633 // Run element filters 3634 if (name in nodeFilters) { 3635 list = matchedNodes[name]; 3636 3637 if (list) 3638 list.push(node); 3639 else 3640 matchedNodes[name] = [node]; 3641 } 3642 3643 // Run attribute filters 3644 i = attributeFilters.length; 3645 while (i--) { 3646 name = attributeFilters[i].name; 3647 3648 if (name in node.attributes.map) { 3649 list = matchedAttributes[name]; 3650 3651 if (list) 3652 list.push(node); 3653 else 3654 matchedAttributes[name] = [node]; 3655 } 3656 } 3657 3658 return node; 3659 }; 3660 3661 self.addNodeFilter = function(name, callback) { 3662 tinymce.each(tinymce.explode(name), function(name) { 3663 var list = nodeFilters[name]; 3664 3665 if (!list) 3666 nodeFilters[name] = list = []; 3667 3668 list.push(callback); 3669 }); 3670 }; 3671 3672 self.addAttributeFilter = function(name, callback) { 3673 tinymce.each(tinymce.explode(name), function(name) { 3674 var i; 3675 3676 for (i = 0; i < attributeFilters.length; i++) { 3677 if (attributeFilters[i].name === name) { 3678 attributeFilters[i].callbacks.push(callback); 3679 return; 3680 } 3681 } 3682 3683 attributeFilters.push({name: name, callbacks: [callback]}); 3684 }); 3685 }; 3686 3687 self.parse = function(html, args) { 3688 var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate, 3689 blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement, 3690 endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName; 3691 3692 args = args || {}; 3693 matchedNodes = {}; 3694 matchedAttributes = {}; 3695 blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements()); 3696 nonEmptyElements = schema.getNonEmptyElements(); 3697 children = schema.children; 3698 validate = settings.validate; 3699 rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block; 3700 3701 whiteSpaceElements = schema.getWhiteSpaceElements(); 3702 startWhiteSpaceRegExp = /^[ \t\r\n]+/; 3703 endWhiteSpaceRegExp = /[ \t\r\n]+$/; 3704 allWhiteSpaceRegExp = /[ \t\r\n]+/g; 3705 isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/; 3706 3707 function addRootBlocks() { 3708 var node = rootNode.firstChild, next, rootBlockNode; 3709 3710 while (node) { 3711 next = node.next; 3712 3713 if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) { 3714 if (!rootBlockNode) { 3715 // Create a new root block element 3716 rootBlockNode = createNode(rootBlockName, 1); 3717 rootNode.insert(rootBlockNode, node); 3718 rootBlockNode.append(node); 3719 } else 3720 rootBlockNode.append(node); 3721 } else { 3722 rootBlockNode = null; 3723 } 3724 3725 node = next; 3726 }; 3727 }; 3728 3729 function createNode(name, type) { 3730 var node = new Node(name, type), list; 3731 3732 if (name in nodeFilters) { 3733 list = matchedNodes[name]; 3734 3735 if (list) 3736 list.push(node); 3737 else 3738 matchedNodes[name] = [node]; 3739 } 3740 3741 return node; 3742 }; 3743 3744 function removeWhitespaceBefore(node) { 3745 var textNode, textVal, sibling; 3746 3747 for (textNode = node.prev; textNode && textNode.type === 3; ) { 3748 textVal = textNode.value.replace(endWhiteSpaceRegExp, ''); 3749 3750 if (textVal.length > 0) { 3751 textNode.value = textVal; 3752 textNode = textNode.prev; 3753 } else { 3754 sibling = textNode.prev; 3755 textNode.remove(); 3756 textNode = sibling; 3757 } 3758 } 3759 }; 3760 3761 function cloneAndExcludeBlocks(input) { 3762 var name, output = {}; 3763 3764 for (name in input) { 3765 if (name !== 'li' && name != 'p') { 3766 output[name] = input[name]; 3767 } 3768 } 3769 3770 return output; 3771 }; 3772 3773 parser = new tinymce.html.SaxParser({ 3774 validate : validate, 3775 3776 // Exclude P and LI from DOM parsing since it's treated better by the DOM parser 3777 self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()), 3778 3779 cdata: function(text) { 3780 node.append(createNode('#cdata', 4)).value = text; 3781 }, 3782 3783 text: function(text, raw) { 3784 var textNode; 3785 3786 // Trim all redundant whitespace on non white space elements 3787 if (!isInWhiteSpacePreservedElement) { 3788 text = text.replace(allWhiteSpaceRegExp, ' '); 3789 3790 if (node.lastChild && blockElements[node.lastChild.name]) 3791 text = text.replace(startWhiteSpaceRegExp, ''); 3792 } 3793 3794 // Do we need to create the node 3795 if (text.length !== 0) { 3796 textNode = createNode('#text', 3); 3797 textNode.raw = !!raw; 3798 node.append(textNode).value = text; 3799 } 3800 }, 3801 3802 comment: function(text) { 3803 node.append(createNode('#comment', 8)).value = text; 3804 }, 3805 3806 pi: function(name, text) { 3807 node.append(createNode(name, 7)).value = text; 3808 removeWhitespaceBefore(node); 3809 }, 3810 3811 doctype: function(text) { 3812 var newNode; 3813 3814 newNode = node.append(createNode('#doctype', 10)); 3815 newNode.value = text; 3816 removeWhitespaceBefore(node); 3817 }, 3818 3819 start: function(name, attrs, empty) { 3820 var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent; 3821 3822 elementRule = validate ? schema.getElementRule(name) : {}; 3823 if (elementRule) { 3824 newNode = createNode(elementRule.outputName || name, 1); 3825 newNode.attributes = attrs; 3826 newNode.shortEnded = empty; 3827 3828 node.append(newNode); 3829 3830 // Check if node is valid child of the parent node is the child is 3831 // unknown we don't collect it since it's probably a custom element 3832 parent = children[node.name]; 3833 if (parent && children[newNode.name] && !parent[newNode.name]) 3834 invalidChildren.push(newNode); 3835 3836 attrFiltersLen = attributeFilters.length; 3837 while (attrFiltersLen--) { 3838 attrName = attributeFilters[attrFiltersLen].name; 3839 3840 if (attrName in attrs.map) { 3841 list = matchedAttributes[attrName]; 3842 3843 if (list) 3844 list.push(newNode); 3845 else 3846 matchedAttributes[attrName] = [newNode]; 3847 } 3848 } 3849 3850 // Trim whitespace before block 3851 if (blockElements[name]) 3852 removeWhitespaceBefore(newNode); 3853 3854 // Change current node if the element wasn't empty i.e not <br /> or <img /> 3855 if (!empty) 3856 node = newNode; 3857 3858 // Check if we are inside a whitespace preserved element 3859 if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) { 3860 isInWhiteSpacePreservedElement = true; 3861 } 3862 } 3863 }, 3864 3865 end: function(name) { 3866 var textNode, elementRule, text, sibling, tempNode; 3867 3868 elementRule = validate ? schema.getElementRule(name) : {}; 3869 if (elementRule) { 3870 if (blockElements[name]) { 3871 if (!isInWhiteSpacePreservedElement) { 3872 // Trim whitespace of the first node in a block 3873 textNode = node.firstChild; 3874 if (textNode && textNode.type === 3) { 3875 text = textNode.value.replace(startWhiteSpaceRegExp, ''); 3876 3877 // Any characters left after trim or should we remove it 3878 if (text.length > 0) { 3879 textNode.value = text; 3880 textNode = textNode.next; 3881 } else { 3882 sibling = textNode.next; 3883 textNode.remove(); 3884 textNode = sibling; 3885 } 3886 3887 // Remove any pure whitespace siblings 3888 while (textNode && textNode.type === 3) { 3889 text = textNode.value; 3890 sibling = textNode.next; 3891 3892 if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) { 3893 textNode.remove(); 3894 textNode = sibling; 3895 } 3896 3897 textNode = sibling; 3898 } 3899 } 3900 3901 // Trim whitespace of the last node in a block 3902 textNode = node.lastChild; 3903 if (textNode && textNode.type === 3) { 3904 text = textNode.value.replace(endWhiteSpaceRegExp, ''); 3905 3906 // Any characters left after trim or should we remove it 3907 if (text.length > 0) { 3908 textNode.value = text; 3909 textNode = textNode.prev; 3910 } else { 3911 sibling = textNode.prev; 3912 textNode.remove(); 3913 textNode = sibling; 3914 } 3915 3916 // Remove any pure whitespace siblings 3917 while (textNode && textNode.type === 3) { 3918 text = textNode.value; 3919 sibling = textNode.prev; 3920 3921 if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) { 3922 textNode.remove(); 3923 textNode = sibling; 3924 } 3925 3926 textNode = sibling; 3927 } 3928 } 3929 } 3930 3931 // Trim start white space 3932 textNode = node.prev; 3933 if (textNode && textNode.type === 3) { 3934 text = textNode.value.replace(startWhiteSpaceRegExp, ''); 3935 3936 if (text.length > 0) 3937 textNode.value = text; 3938 else 3939 textNode.remove(); 3940 } 3941 } 3942 3943 // Check if we exited a whitespace preserved element 3944 if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) { 3945 isInWhiteSpacePreservedElement = false; 3946 } 3947 3948 // Handle empty nodes 3949 if (elementRule.removeEmpty || elementRule.paddEmpty) { 3950 if (node.isEmpty(nonEmptyElements)) { 3951 if (elementRule.paddEmpty) 3952 node.empty().append(new Node('#text', '3')).value = '\u00a0'; 3953 else { 3954 // Leave nodes that have a name like <a name="name"> 3955 if (!node.attributes.map.name && !node.attributes.map.id) { 3956 tempNode = node.parent; 3957 node.empty().remove(); 3958 node = tempNode; 3959 return; 3960 } 3961 } 3962 } 3963 } 3964 3965 node = node.parent; 3966 } 3967 } 3968 }, schema); 3969 3970 rootNode = node = new Node(args.context || settings.root_name, 11); 3971 3972 parser.parse(html); 3973 3974 // Fix invalid children or report invalid children in a contextual parsing 3975 if (validate && invalidChildren.length) { 3976 if (!args.context) 3977 fixInvalidChildren(invalidChildren); 3978 else 3979 args.invalid = true; 3980 } 3981 3982 // Wrap nodes in the root into block elements if the root is body 3983 if (rootBlockName && rootNode.name == 'body') 3984 addRootBlocks(); 3985 3986 // Run filters only when the contents is valid 3987 if (!args.invalid) { 3988 // Run node filters 3989 for (name in matchedNodes) { 3990 list = nodeFilters[name]; 3991 nodes = matchedNodes[name]; 3992 3993 // Remove already removed children 3994 fi = nodes.length; 3995 while (fi--) { 3996 if (!nodes[fi].parent) 3997 nodes.splice(fi, 1); 3998 } 3999 4000 for (i = 0, l = list.length; i < l; i++) 4001 list[i](nodes, name, args); 4002 } 4003 4004 // Run attribute filters 4005 for (i = 0, l = attributeFilters.length; i < l; i++) { 4006 list = attributeFilters[i]; 4007 4008 if (list.name in matchedAttributes) { 4009 nodes = matchedAttributes[list.name]; 4010 4011 // Remove already removed children 4012 fi = nodes.length; 4013 while (fi--) { 4014 if (!nodes[fi].parent) 4015 nodes.splice(fi, 1); 4016 } 4017 4018 for (fi = 0, fl = list.callbacks.length; fi < fl; fi++) 4019 list.callbacks[fi](nodes, list.name, args); 4020 } 4021 } 4022 } 4023 4024 return rootNode; 4025 }; 4026 4027 // Remove <br> at end of block elements Gecko and WebKit injects BR elements to 4028 // make it possible to place the caret inside empty blocks. This logic tries to remove 4029 // these elements and keep br elements that where intended to be there intact 4030 if (settings.remove_trailing_brs) { 4031 self.addNodeFilter('br', function(nodes, name) { 4032 var i, l = nodes.length, node, blockElements = tinymce.extend({}, schema.getBlockElements()), 4033 nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName; 4034 4035 // Remove brs from body element as well 4036 blockElements.body = 1; 4037 4038 // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p> 4039 for (i = 0; i < l; i++) { 4040 node = nodes[i]; 4041 parent = node.parent; 4042 4043 if (blockElements[node.parent.name] && node === parent.lastChild) { 4044 // Loop all nodes to the left of the current node and check for other BR elements 4045 // excluding bookmarks since they are invisible 4046 prev = node.prev; 4047 while (prev) { 4048 prevName = prev.name; 4049 4050 // Ignore bookmarks 4051 if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') { 4052 // Found a non BR element 4053 if (prevName !== "br") 4054 break; 4055 4056 // Found another br it's a <br><br> structure then don't remove anything 4057 if (prevName === 'br') { 4058 node = null; 4059 break; 4060 } 4061 } 4062 4063 prev = prev.prev; 4064 } 4065 4066 if (node) { 4067 node.remove(); 4068 4069 // Is the parent to be considered empty after we removed the BR 4070 if (parent.isEmpty(nonEmptyElements)) { 4071 elementRule = schema.getElementRule(parent.name); 4072 4073 // Remove or padd the element depending on schema rule 4074 if (elementRule) { 4075 if (elementRule.removeEmpty) 4076 parent.remove(); 4077 else if (elementRule.paddEmpty) 4078 parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0'; 4079 } 4080 } 4081 } 4082 } else { 4083 // Replaces BR elements inside inline elements like <p><b><i><br></i></b></p> so they become <p><b><i> </i></b></p> 4084 lastParent = node; 4085 while (parent.firstChild === lastParent && parent.lastChild === lastParent) { 4086 lastParent = parent; 4087 4088 if (blockElements[parent.name]) { 4089 break; 4090 } 4091 4092 parent = parent.parent; 4093 } 4094 4095 if (lastParent === parent) { 4096 textNode = new tinymce.html.Node('#text', 3); 4097 textNode.value = '\u00a0'; 4098 node.replace(textNode); 4099 } 4100 } 4101 } 4102 }); 4103 } 4104 4105 // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included. 4106 if (!settings.allow_html_in_named_anchor) { 4107 self.addAttributeFilter('id,name', function(nodes, name) { 4108 var i = nodes.length, sibling, prevSibling, parent, node; 4109 4110 while (i--) { 4111 node = nodes[i]; 4112 if (node.name === 'a' && node.firstChild && !node.attr('href')) { 4113 parent = node.parent; 4114 4115 // Move children after current node 4116 sibling = node.lastChild; 4117 do { 4118 prevSibling = sibling.prev; 4119 parent.insert(sibling, node); 4120 sibling = prevSibling; 4121 } while (sibling); 4122 } 4123 } 4124 }); 4125 } 4126 } 4127 })(tinymce); 4128 4129 tinymce.html.Writer = function(settings) { 4130 var html = [], indent, indentBefore, indentAfter, encode, htmlOutput; 4131 4132 settings = settings || {}; 4133 indent = settings.indent; 4134 indentBefore = tinymce.makeMap(settings.indent_before || ''); 4135 indentAfter = tinymce.makeMap(settings.indent_after || ''); 4136 encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities); 4137 htmlOutput = settings.element_format == "html"; 4138 4139 return { 4140 start: function(name, attrs, empty) { 4141 var i, l, attr, value; 4142 4143 if (indent && indentBefore[name] && html.length > 0) { 4144 value = html[html.length - 1]; 4145 4146 if (value.length > 0 && value !== '\n') 4147 html.push('\n'); 4148 } 4149 4150 html.push('<', name); 4151 4152 if (attrs) { 4153 for (i = 0, l = attrs.length; i < l; i++) { 4154 attr = attrs[i]; 4155 html.push(' ', attr.name, '="', encode(attr.value, true), '"'); 4156 } 4157 } 4158 4159 if (!empty || htmlOutput) 4160 html[html.length] = '>'; 4161 else 4162 html[html.length] = ' />'; 4163 4164 if (empty && indent && indentAfter[name] && html.length > 0) { 4165 value = html[html.length - 1]; 4166 4167 if (value.length > 0 && value !== '\n') 4168 html.push('\n'); 4169 } 4170 }, 4171 4172 end: function(name) { 4173 var value; 4174 4175 /*if (indent && indentBefore[name] && html.length > 0) { 4176 value = html[html.length - 1]; 4177 4178 if (value.length > 0 && value !== '\n') 4179 html.push('\n'); 4180 }*/ 4181 4182 html.push('</', name, '>'); 4183 4184 if (indent && indentAfter[name] && html.length > 0) { 4185 value = html[html.length - 1]; 4186 4187 if (value.length > 0 && value !== '\n') 4188 html.push('\n'); 4189 } 4190 }, 4191 4192 text: function(text, raw) { 4193 if (text.length > 0) 4194 html[html.length] = raw ? text : encode(text); 4195 }, 4196 4197 cdata: function(text) { 4198 html.push('<![CDATA[', text, ']]>'); 4199 }, 4200 4201 comment: function(text) { 4202 html.push('<!--', text, '-->'); 4203 }, 4204 4205 pi: function(name, text) { 4206 if (text) 4207 html.push('<?', name, ' ', text, '?>'); 4208 else 4209 html.push('<?', name, '?>'); 4210 4211 if (indent) 4212 html.push('\n'); 4213 }, 4214 4215 doctype: function(text) { 4216 html.push('<!DOCTYPE', text, '>', indent ? '\n' : ''); 4217 }, 4218 4219 reset: function() { 4220 html.length = 0; 4221 }, 4222 4223 getContent: function() { 4224 return html.join('').replace(/\n$/, ''); 4225 } 4226 }; 4227 }; 4228 4229 (function(tinymce) { 4230 tinymce.html.Serializer = function(settings, schema) { 4231 var self = this, writer = new tinymce.html.Writer(settings); 4232 4233 settings = settings || {}; 4234 settings.validate = "validate" in settings ? settings.validate : true; 4235 4236 self.schema = schema = schema || new tinymce.html.Schema(); 4237 self.writer = writer; 4238 4239 self.serialize = function(node) { 4240 var handlers, validate; 4241 4242 validate = settings.validate; 4243 4244 handlers = { 4245 // #text 4246 3: function(node, raw) { 4247 writer.text(node.value, node.raw); 4248 }, 4249 4250 // #comment 4251 8: function(node) { 4252 writer.comment(node.value); 4253 }, 4254 4255 // Processing instruction 4256 7: function(node) { 4257 writer.pi(node.name, node.value); 4258 }, 4259 4260 // Doctype 4261 10: function(node) { 4262 writer.doctype(node.value); 4263 }, 4264 4265 // CDATA 4266 4: function(node) { 4267 writer.cdata(node.value); 4268 }, 4269 4270 // Document fragment 4271 11: function(node) { 4272 if ((node = node.firstChild)) { 4273 do { 4274 walk(node); 4275 } while (node = node.next); 4276 } 4277 } 4278 }; 4279 4280 writer.reset(); 4281 4282 function walk(node) { 4283 var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule; 4284 4285 if (!handler) { 4286 name = node.name; 4287 isEmpty = node.shortEnded; 4288 attrs = node.attributes; 4289 4290 // Sort attributes 4291 if (validate && attrs && attrs.length > 1) { 4292 sortedAttrs = []; 4293 sortedAttrs.map = {}; 4294 4295 elementRule = schema.getElementRule(node.name); 4296 for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) { 4297 attrName = elementRule.attributesOrder[i]; 4298 4299 if (attrName in attrs.map) { 4300 attrValue = attrs.map[attrName]; 4301 sortedAttrs.map[attrName] = attrValue; 4302 sortedAttrs.push({name: attrName, value: attrValue}); 4303 } 4304 } 4305 4306 for (i = 0, l = attrs.length; i < l; i++) { 4307 attrName = attrs[i].name; 4308 4309 if (!(attrName in sortedAttrs.map)) { 4310 attrValue = attrs.map[attrName]; 4311 sortedAttrs.map[attrName] = attrValue; 4312 sortedAttrs.push({name: attrName, value: attrValue}); 4313 } 4314 } 4315 4316 attrs = sortedAttrs; 4317 } 4318 4319 writer.start(node.name, attrs, isEmpty); 4320 4321 if (!isEmpty) { 4322 if ((node = node.firstChild)) { 4323 do { 4324 walk(node); 4325 } while (node = node.next); 4326 } 4327 4328 writer.end(name); 4329 } 4330 } else 4331 handler(node); 4332 } 4333 4334 // Serialize element and treat all non elements as fragments 4335 if (node.type == 1 && !settings.inner) 4336 walk(node); 4337 else 4338 handlers[11](node); 4339 4340 return writer.getContent(); 4341 }; 4342 } 4343 })(tinymce); 4344 4345 // JSLint defined globals 4346 /*global tinymce:false, window:false */ 4347 4348 tinymce.dom = {}; 4349 4350 (function(namespace, expando) { 4351 var w3cEventModel = !!document.addEventListener; 4352 4353 function addEvent(target, name, callback, capture) { 4354 if (target.addEventListener) { 4355 target.addEventListener(name, callback, capture || false); 4356 } else if (target.attachEvent) { 4357 target.attachEvent('on' + name, callback); 4358 } 4359 } 4360 4361 function removeEvent(target, name, callback, capture) { 4362 if (target.removeEventListener) { 4363 target.removeEventListener(name, callback, capture || false); 4364 } else if (target.detachEvent) { 4365 target.detachEvent('on' + name, callback); 4366 } 4367 } 4368 4369 function fix(original_event, data) { 4370 var name, event = data || {}; 4371 4372 // Dummy function that gets replaced on the delegation state functions 4373 function returnFalse() { 4374 return false; 4375 } 4376 4377 // Dummy function that gets replaced on the delegation state functions 4378 function returnTrue() { 4379 return true; 4380 } 4381 4382 // Copy all properties from the original event 4383 for (name in original_event) { 4384 // layerX/layerY is deprecated in Chrome and produces a warning 4385 if (name !== "layerX" && name !== "layerY") { 4386 event[name] = original_event[name]; 4387 } 4388 } 4389 4390 // Normalize target IE uses srcElement 4391 if (!event.target) { 4392 event.target = event.srcElement || document; 4393 } 4394 4395 // Add preventDefault method 4396 event.preventDefault = function() { 4397 event.isDefaultPrevented = returnTrue; 4398 4399 // Execute preventDefault on the original event object 4400 if (original_event) { 4401 if (original_event.preventDefault) { 4402 original_event.preventDefault(); 4403 } else { 4404 original_event.returnValue = false; // IE 4405 } 4406 } 4407 }; 4408 4409 // Add stopPropagation 4410 event.stopPropagation = function() { 4411 event.isPropagationStopped = returnTrue; 4412 4413 // Execute stopPropagation on the original event object 4414 if (original_event) { 4415 if (original_event.stopPropagation) { 4416 original_event.stopPropagation(); 4417 } else { 4418 original_event.cancelBubble = true; // IE 4419 } 4420 } 4421 }; 4422 4423 // Add stopImmediatePropagation 4424 event.stopImmediatePropagation = function() { 4425 event.isImmediatePropagationStopped = returnTrue; 4426 event.stopPropagation(); 4427 }; 4428 4429 // Add event delegation states 4430 if (!event.isDefaultPrevented) { 4431 event.isDefaultPrevented = returnFalse; 4432 event.isPropagationStopped = returnFalse; 4433 event.isImmediatePropagationStopped = returnFalse; 4434 } 4435 4436 return event; 4437 } 4438 4439 function bindOnReady(win, callback, event_utils) { 4440 var doc = win.document, event = {type: 'ready'}; 4441 4442 // Gets called when the DOM is ready 4443 function readyHandler() { 4444 if (!event_utils.domLoaded) { 4445 event_utils.domLoaded = true; 4446 callback(event); 4447 } 4448 } 4449 4450 // Use W3C method 4451 if (w3cEventModel) { 4452 addEvent(win, 'DOMContentLoaded', readyHandler); 4453 } else { 4454 // Use IE method 4455 addEvent(doc, "readystatechange", function() { 4456 if (doc.readyState === "complete") { 4457 removeEvent(doc, "readystatechange", arguments.callee); 4458 readyHandler(); 4459 } 4460 }); 4461 4462 // Wait until we can scroll, when we can the DOM is initialized 4463 if (doc.documentElement.doScroll && win === win.top) { 4464 (function() { 4465 try { 4466 // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author. 4467 // http://javascript.nwbox.com/IEContentLoaded/ 4468 doc.documentElement.doScroll("left"); 4469 } catch (ex) { 4470 setTimeout(arguments.callee, 0); 4471 return; 4472 } 4473 4474 readyHandler(); 4475 })(); 4476 } 4477 } 4478 4479 // Fallback if any of the above methods should fail for some odd reason 4480 addEvent(win, 'load', readyHandler); 4481 } 4482 4483 function EventUtils(proxy) { 4484 var self = this, events = {}, count, isFocusBlurBound, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave; 4485 4486 hasMouseEnterLeave = "onmouseenter" in document.documentElement; 4487 hasFocusIn = "onfocusin" in document.documentElement; 4488 mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'}; 4489 count = 1; 4490 4491 // State if the DOMContentLoaded was executed or not 4492 self.domLoaded = false; 4493 self.events = events; 4494 4495 function executeHandlers(evt, id) { 4496 var callbackList, i, l, callback; 4497 4498 callbackList = events[id][evt.type]; 4499 if (callbackList) { 4500 for (i = 0, l = callbackList.length; i < l; i++) { 4501 callback = callbackList[i]; 4502 4503 // Check if callback exists might be removed if a unbind is called inside the callback 4504 if (callback && callback.func.call(callback.scope, evt) === false) { 4505 evt.preventDefault(); 4506 } 4507 4508 // Should we stop propagation to immediate listeners 4509 if (evt.isImmediatePropagationStopped()) { 4510 return; 4511 } 4512 } 4513 } 4514 } 4515 4516 self.bind = function(target, names, callback, scope) { 4517 var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window; 4518 4519 // Native event handler function patches the event and executes the callbacks for the expando 4520 function defaultNativeHandler(evt) { 4521 executeHandlers(fix(evt || win.event), id); 4522 } 4523 4524 // Don't bind to text nodes or comments 4525 if (!target || target.nodeType === 3 || target.nodeType === 8) { 4526 return; 4527 } 4528 4529 // Create or get events id for the target 4530 if (!target[expando]) { 4531 id = count++; 4532 target[expando] = id; 4533 events[id] = {}; 4534 } else { 4535 id = target[expando]; 4536 4537 if (!events[id]) { 4538 events[id] = {}; 4539 } 4540 } 4541 4542 // Setup the specified scope or use the target as a default 4543 scope = scope || target; 4544 4545 // Split names and bind each event, enables you to bind multiple events with one call 4546 names = names.split(' '); 4547 i = names.length; 4548 while (i--) { 4549 name = names[i]; 4550 nativeHandler = defaultNativeHandler; 4551 fakeName = capture = false; 4552 4553 // Use ready instead of DOMContentLoaded 4554 if (name === "DOMContentLoaded") { 4555 name = "ready"; 4556 } 4557 4558 // DOM is already ready 4559 if ((self.domLoaded || target.readyState == 'complete') && name === "ready") { 4560 self.domLoaded = true; 4561 callback.call(scope, fix({type: name})); 4562 continue; 4563 } 4564 4565 // Handle mouseenter/mouseleaver 4566 if (!hasMouseEnterLeave) { 4567 fakeName = mouseEnterLeave[name]; 4568 4569 if (fakeName) { 4570 nativeHandler = function(evt) { 4571 var current, related; 4572 4573 current = evt.currentTarget; 4574 related = evt.relatedTarget; 4575 4576 // Check if related is inside the current target if it's not then the event should be ignored since it's a mouseover/mouseout inside the element 4577 if (related && current.contains) { 4578 // Use contains for performance 4579 related = current.contains(related); 4580 } else { 4581 while (related && related !== current) { 4582 related = related.parentNode; 4583 } 4584 } 4585 4586 // Fire fake event 4587 if (!related) { 4588 evt = fix(evt || win.event); 4589 evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter'; 4590 evt.target = current; 4591 executeHandlers(evt, id); 4592 } 4593 }; 4594 } 4595 } 4596 4597 // Fake bubbeling of focusin/focusout 4598 if (!hasFocusIn && (name === "focusin" || name === "focusout")) { 4599 capture = true; 4600 fakeName = name === "focusin" ? "focus" : "blur"; 4601 nativeHandler = function(evt) { 4602 evt = fix(evt || win.event); 4603 evt.type = evt.type === 'focus' ? 'focusin' : 'focusout'; 4604 executeHandlers(evt, id); 4605 }; 4606 } 4607 4608 // Setup callback list and bind native event 4609 callbackList = events[id][name]; 4610 if (!callbackList) { 4611 events[id][name] = callbackList = [{func: callback, scope: scope}]; 4612 callbackList.fakeName = fakeName; 4613 callbackList.capture = capture; 4614 4615 // Add the nativeHandler to the callback list so that we can later unbind it 4616 callbackList.nativeHandler = nativeHandler; 4617 if (!w3cEventModel) { 4618 callbackList.proxyHandler = proxy(id); 4619 } 4620 4621 // Check if the target has native events support 4622 if (name === "ready") { 4623 bindOnReady(target, nativeHandler, self); 4624 } else { 4625 addEvent(target, fakeName || name, w3cEventModel ? nativeHandler : callbackList.proxyHandler, capture); 4626 } 4627 } else { 4628 // If it already has an native handler then just push the callback 4629 callbackList.push({func: callback, scope: scope}); 4630 } 4631 } 4632 4633 target = callbackList = 0; // Clean memory for IE 4634 4635 return callback; 4636 }; 4637 4638 self.unbind = function(target, names, callback) { 4639 var id, callbackList, i, ci, name, eventMap; 4640 4641 // Don't bind to text nodes or comments 4642 if (!target || target.nodeType === 3 || target.nodeType === 8) { 4643 return self; 4644 } 4645 4646 // Unbind event or events if the target has the expando 4647 id = target[expando]; 4648 if (id) { 4649 eventMap = events[id]; 4650 4651 // Specific callback 4652 if (names) { 4653 names = names.split(' '); 4654 i = names.length; 4655 while (i--) { 4656 name = names[i]; 4657 callbackList = eventMap[name]; 4658 4659 // Unbind the event if it exists in the map 4660 if (callbackList) { 4661 // Remove specified callback 4662 if (callback) { 4663 ci = callbackList.length; 4664 while (ci--) { 4665 if (callbackList[ci].func === callback) { 4666 callbackList.splice(ci, 1); 4667 } 4668 } 4669 } 4670 4671 // Remove all callbacks if there isn't a specified callback or there is no callbacks left 4672 if (!callback || callbackList.length === 0) { 4673 delete eventMap[name]; 4674 removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture); 4675 } 4676 } 4677 } 4678 } else { 4679 // All events for a specific element 4680 for (name in eventMap) { 4681 callbackList = eventMap[name]; 4682 removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture); 4683 } 4684 4685 eventMap = {}; 4686 } 4687 4688 // Check if object is empty, if it isn't then we won't remove the expando map 4689 for (name in eventMap) { 4690 return self; 4691 } 4692 4693 // Delete event object 4694 delete events[id]; 4695 4696 // Remove expando from target 4697 try { 4698 // IE will fail here since it can't delete properties from window 4699 delete target[expando]; 4700 } catch (ex) { 4701 // IE will set it to null 4702 target[expando] = null; 4703 } 4704 } 4705 4706 return self; 4707 }; 4708 4709 self.fire = function(target, name, args) { 4710 var id, event; 4711 4712 // Don't bind to text nodes or comments 4713 if (!target || target.nodeType === 3 || target.nodeType === 8) { 4714 return self; 4715 } 4716 4717 // Build event object by patching the args 4718 event = fix(null, args); 4719 event.type = name; 4720 4721 do { 4722 // Found an expando that means there is listeners to execute 4723 id = target[expando]; 4724 if (id) { 4725 executeHandlers(event, id); 4726 } 4727 4728 // Walk up the DOM 4729 target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow; 4730 } while (target && !event.isPropagationStopped()); 4731 4732 return self; 4733 }; 4734 4735 self.clean = function(target) { 4736 var i, children, unbind = self.unbind; 4737 4738 // Don't bind to text nodes or comments 4739 if (!target || target.nodeType === 3 || target.nodeType === 8) { 4740 return self; 4741 } 4742 4743 // Unbind any element on the specificed target 4744 if (target[expando]) { 4745 unbind(target); 4746 } 4747 4748 // Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children 4749 if (!target.getElementsByTagName) { 4750 target = target.document; 4751 } 4752 4753 // Remove events from each child element 4754 if (target && target.getElementsByTagName) { 4755 unbind(target); 4756 4757 children = target.getElementsByTagName('*'); 4758 i = children.length; 4759 while (i--) { 4760 target = children[i]; 4761 4762 if (target[expando]) { 4763 unbind(target); 4764 } 4765 } 4766 } 4767 4768 return self; 4769 }; 4770 4771 self.callNativeHandler = function(id, evt) { 4772 if (events) { 4773 events[id][evt.type].nativeHandler(evt); 4774 } 4775 }; 4776 4777 self.destory = function() { 4778 events = {}; 4779 }; 4780 4781 // Legacy function calls 4782 4783 self.add = function(target, events, func, scope) { 4784 // Old API supported direct ID assignment 4785 if (typeof(target) === "string") { 4786 target = document.getElementById(target); 4787 } 4788 4789 // Old API supported multiple targets 4790 if (target && target instanceof Array) { 4791 var i = target.length; 4792 4793 while (i--) { 4794 self.add(target[i], events, func, scope); 4795 } 4796 4797 return; 4798 } 4799 4800 // Old API called ready init 4801 if (events === "init") { 4802 events = "ready"; 4803 } 4804 4805 return self.bind(target, events instanceof Array ? events.join(' ') : events, func, scope); 4806 }; 4807 4808 self.remove = function(target, events, func, scope) { 4809 if (!target) { 4810 return self; 4811 } 4812 4813 // Old API supported direct ID assignment 4814 if (typeof(target) === "string") { 4815 target = document.getElementById(target); 4816 } 4817 4818 // Old API supported multiple targets 4819 if (target instanceof Array) { 4820 var i = target.length; 4821 4822 while (i--) { 4823 self.remove(target[i], events, func, scope); 4824 } 4825 4826 return self; 4827 } 4828 4829 return self.unbind(target, events instanceof Array ? events.join(' ') : events, func); 4830 }; 4831 4832 self.clear = function(target) { 4833 // Old API supported direct ID assignment 4834 if (typeof(target) === "string") { 4835 target = document.getElementById(target); 4836 } 4837 4838 return self.clean(target); 4839 }; 4840 4841 self.cancel = function(e) { 4842 if (e) { 4843 self.prevent(e); 4844 self.stop(e); 4845 } 4846 4847 return false; 4848 }; 4849 4850 self.prevent = function(e) { 4851 if (!e.preventDefault) { 4852 e = fix(e); 4853 } 4854 4855 e.preventDefault(); 4856 4857 return false; 4858 }; 4859 4860 self.stop = function(e) { 4861 if (!e.stopPropagation) { 4862 e = fix(e); 4863 } 4864 4865 e.stopPropagation(); 4866 4867 return false; 4868 }; 4869 } 4870 4871 namespace.EventUtils = EventUtils; 4872 4873 namespace.Event = new EventUtils(function(id) { 4874 return function(evt) { 4875 tinymce.dom.Event.callNativeHandler(id, evt); 4876 }; 4877 }); 4878 4879 // Bind ready event when tinymce script is loaded 4880 namespace.Event.bind(window, 'ready', function() {}); 4881 4882 namespace = 0; 4883 })(tinymce.dom, 'data-mce-expando'); // Namespace and expando 4884 4885 tinymce.dom.TreeWalker = function(start_node, root_node) { 4886 var node = start_node; 4887 4888 function findSibling(node, start_name, sibling_name, shallow) { 4889 var sibling, parent; 4890 4891 if (node) { 4892 // Walk into nodes if it has a start 4893 if (!shallow && node[start_name]) 4894 return node[start_name]; 4895 4896 // Return the sibling if it has one 4897 if (node != root_node) { 4898 sibling = node[sibling_name]; 4899 if (sibling) 4900 return sibling; 4901 4902 // Walk up the parents to look for siblings 4903 for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) { 4904 sibling = parent[sibling_name]; 4905 if (sibling) 4906 return sibling; 4907 } 4908 } 4909 } 4910 }; 4911 4912 this.current = function() { 4913 return node; 4914 }; 4915 4916 this.next = function(shallow) { 4917 return (node = findSibling(node, 'firstChild', 'nextSibling', shallow)); 4918 }; 4919 4920 this.prev = function(shallow) { 4921 return (node = findSibling(node, 'lastChild', 'previousSibling', shallow)); 4922 }; 4923 }; 4924 4925 (function(tinymce) { 4926 // Shorten names 4927 var each = tinymce.each, 4928 is = tinymce.is, 4929 isWebKit = tinymce.isWebKit, 4930 isIE = tinymce.isIE, 4931 Entities = tinymce.html.Entities, 4932 simpleSelectorRe = /^([a-z0-9],?)+$/i, 4933 whiteSpaceRegExp = /^[ \t\r\n]*$/; 4934 4935 tinymce.create('tinymce.dom.DOMUtils', { 4936 doc : null, 4937 root : null, 4938 files : null, 4939 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/, 4940 props : { 4941 "for" : "htmlFor", 4942 "class" : "className", 4943 className : "className", 4944 checked : "checked", 4945 disabled : "disabled", 4946 maxlength : "maxLength", 4947 readonly : "readOnly", 4948 selected : "selected", 4949 value : "value", 4950 id : "id", 4951 name : "name", 4952 type : "type" 4953 }, 4954 4955 DOMUtils : function(d, s) { 4956 var t = this, globalStyle, name, blockElementsMap; 4957 4958 t.doc = d; 4959 t.win = window; 4960 t.files = {}; 4961 t.cssFlicker = false; 4962 t.counter = 0; 4963 t.stdMode = !tinymce.isIE || d.documentMode >= 8; 4964 t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode; 4965 t.hasOuterHTML = "outerHTML" in d.createElement("a"); 4966 4967 t.settings = s = tinymce.extend({ 4968 keep_values : false, 4969 hex_colors : 1 4970 }, s); 4971 4972 t.schema = s.schema; 4973 t.styles = new tinymce.html.Styles({ 4974 url_converter : s.url_converter, 4975 url_converter_scope : s.url_converter_scope 4976 }, s.schema); 4977 4978 // Fix IE6SP2 flicker and check it failed for pre SP2 4979 if (tinymce.isIE6) { 4980 try { 4981 d.execCommand('BackgroundImageCache', false, true); 4982 } catch (e) { 4983 t.cssFlicker = true; 4984 } 4985 } 4986 4987 t.fixDoc(d); 4988 t.events = s.ownEvents ? new tinymce.dom.EventUtils(s.proxy) : tinymce.dom.Event; 4989 tinymce.addUnload(t.destroy, t); 4990 blockElementsMap = s.schema ? s.schema.getBlockElements() : {}; 4991 4992 t.isBlock = function(node) { 4993 // This function is called in module pattern style since it might be executed with the wrong this scope 4994 var type = node.nodeType; 4995 4996 // If it's a node then check the type and use the nodeName 4997 if (type) 4998 return !!(type === 1 && blockElementsMap[node.nodeName]); 4999 5000 return !!blockElementsMap[node]; 5001 }; 5002 }, 5003 5004 fixDoc: function(doc) { 5005 var settings = this.settings, name; 5006 5007 if (isIE && settings.schema) { 5008 // Add missing HTML 4/5 elements to IE 5009 ('abbr article aside audio canvas ' + 5010 'details figcaption figure footer ' + 5011 'header hgroup mark menu meter nav ' + 5012 'output progress section summary ' + 5013 'time video').replace(/\w+/g, function(name) { 5014 doc.createElement(name); 5015 }); 5016 5017 // Create all custom elements 5018 for (name in settings.schema.getCustomElements()) { 5019 doc.createElement(name); 5020 } 5021 } 5022 }, 5023 5024 clone: function(node, deep) { 5025 var self = this, clone, doc; 5026 5027 // TODO: Add feature detection here in the future 5028 if (!isIE || node.nodeType !== 1 || deep) { 5029 return node.cloneNode(deep); 5030 } 5031 5032 doc = self.doc; 5033 5034 // Make a HTML5 safe shallow copy 5035 if (!deep) { 5036 clone = doc.createElement(node.nodeName); 5037 5038 // Copy attribs 5039 each(self.getAttribs(node), function(attr) { 5040 self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName)); 5041 }); 5042 5043 return clone; 5044 } 5045 /* 5046 // Setup HTML5 patched document fragment 5047 if (!self.frag) { 5048 self.frag = doc.createDocumentFragment(); 5049 self.fixDoc(self.frag); 5050 } 5051 5052 // Make a deep copy by adding it to the document fragment then removing it this removed the :section 5053 clone = doc.createElement('div'); 5054 self.frag.appendChild(clone); 5055 clone.innerHTML = node.outerHTML; 5056 self.frag.removeChild(clone); 5057 */ 5058 return clone.firstChild; 5059 }, 5060 5061 getRoot : function() { 5062 var t = this, s = t.settings; 5063 5064 return (s && t.get(s.root_element)) || t.doc.body; 5065 }, 5066 5067 getViewPort : function(w) { 5068 var d, b; 5069 5070 w = !w ? this.win : w; 5071 d = w.document; 5072 b = this.boxModel ? d.documentElement : d.body; 5073 5074 // Returns viewport size excluding scrollbars 5075 return { 5076 x : w.pageXOffset || b.scrollLeft, 5077 y : w.pageYOffset || b.scrollTop, 5078 w : w.innerWidth || b.clientWidth, 5079 h : w.innerHeight || b.clientHeight 5080 }; 5081 }, 5082 5083 getRect : function(e) { 5084 var p, t = this, sr; 5085 5086 e = t.get(e); 5087 p = t.getPos(e); 5088 sr = t.getSize(e); 5089 5090 return { 5091 x : p.x, 5092 y : p.y, 5093 w : sr.w, 5094 h : sr.h 5095 }; 5096 }, 5097 5098 getSize : function(e) { 5099 var t = this, w, h; 5100 5101 e = t.get(e); 5102 w = t.getStyle(e, 'width'); 5103 h = t.getStyle(e, 'height'); 5104 5105 // Non pixel value, then force offset/clientWidth 5106 if (w.indexOf('px') === -1) 5107 w = 0; 5108 5109 // Non pixel value, then force offset/clientWidth 5110 if (h.indexOf('px') === -1) 5111 h = 0; 5112 5113 return { 5114 w : parseInt(w, 10) || e.offsetWidth || e.clientWidth, 5115 h : parseInt(h, 10) || e.offsetHeight || e.clientHeight 5116 }; 5117 }, 5118 5119 getParent : function(n, f, r) { 5120 return this.getParents(n, f, r, false); 5121 }, 5122 5123 getParents : function(n, f, r, c) { 5124 var t = this, na, se = t.settings, o = []; 5125 5126 n = t.get(n); 5127 c = c === undefined; 5128 5129 if (se.strict_root) 5130 r = r || t.getRoot(); 5131 5132 // Wrap node name as func 5133 if (is(f, 'string')) { 5134 na = f; 5135 5136 if (f === '*') { 5137 f = function(n) {return n.nodeType == 1;}; 5138 } else { 5139 f = function(n) { 5140 return t.is(n, na); 5141 }; 5142 } 5143 } 5144 5145 while (n) { 5146 if (n == r || !n.nodeType || n.nodeType === 9) 5147 break; 5148 5149 if (!f || f(n)) { 5150 if (c) 5151 o.push(n); 5152 else 5153 return n; 5154 } 5155 5156 n = n.parentNode; 5157 } 5158 5159 return c ? o : null; 5160 }, 5161 5162 get : function(e) { 5163 var n; 5164 5165 if (e && this.doc && typeof(e) == 'string') { 5166 n = e; 5167 e = this.doc.getElementById(e); 5168 5169 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick 5170 if (e && e.id !== n) 5171 return this.doc.getElementsByName(n)[1]; 5172 } 5173 5174 return e; 5175 }, 5176 5177 getNext : function(node, selector) { 5178 return this._findSib(node, selector, 'nextSibling'); 5179 }, 5180 5181 getPrev : function(node, selector) { 5182 return this._findSib(node, selector, 'previousSibling'); 5183 }, 5184 5185 5186 select : function(pa, s) { 5187 var t = this; 5188 5189 return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []); 5190 }, 5191 5192 is : function(n, selector) { 5193 var i; 5194 5195 // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance 5196 if (n.length === undefined) { 5197 // Simple all selector 5198 if (selector === '*') 5199 return n.nodeType == 1; 5200 5201 // Simple selector just elements 5202 if (simpleSelectorRe.test(selector)) { 5203 selector = selector.toLowerCase().split(/,/); 5204 n = n.nodeName.toLowerCase(); 5205 5206 for (i = selector.length - 1; i >= 0; i--) { 5207 if (selector[i] == n) 5208 return true; 5209 } 5210 5211 return false; 5212 } 5213 } 5214 5215 return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0; 5216 }, 5217 5218 5219 add : function(p, n, a, h, c) { 5220 var t = this; 5221 5222 return this.run(p, function(p) { 5223 var e, k; 5224 5225 e = is(n, 'string') ? t.doc.createElement(n) : n; 5226 t.setAttribs(e, a); 5227 5228 if (h) { 5229 if (h.nodeType) 5230 e.appendChild(h); 5231 else 5232 t.setHTML(e, h); 5233 } 5234 5235 return !c ? p.appendChild(e) : e; 5236 }); 5237 }, 5238 5239 create : function(n, a, h) { 5240 return this.add(this.doc.createElement(n), n, a, h, 1); 5241 }, 5242 5243 createHTML : function(n, a, h) { 5244 var o = '', t = this, k; 5245 5246 o += '<' + n; 5247 5248 for (k in a) { 5249 if (a.hasOwnProperty(k)) 5250 o += ' ' + k + '="' + t.encode(a[k]) + '"'; 5251 } 5252 5253 // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime 5254 if (typeof(h) != "undefined") 5255 return o + '>' + h + '</' + n + '>'; 5256 5257 return o + ' />'; 5258 }, 5259 5260 remove : function(node, keep_children) { 5261 return this.run(node, function(node) { 5262 var child, parent = node.parentNode; 5263 5264 if (!parent) 5265 return null; 5266 5267 if (keep_children) { 5268 while (child = node.firstChild) { 5269 // IE 8 will crash if you don't remove completely empty text nodes 5270 if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue) 5271 parent.insertBefore(child, node); 5272 else 5273 node.removeChild(child); 5274 } 5275 } 5276 5277 return parent.removeChild(node); 5278 }); 5279 }, 5280 5281 setStyle : function(n, na, v) { 5282 var t = this; 5283 5284 return t.run(n, function(e) { 5285 var s, i; 5286 5287 s = e.style; 5288 5289 // Camelcase it, if needed 5290 na = na.replace(/-(\D)/g, function(a, b){ 5291 return b.toUpperCase(); 5292 }); 5293 5294 // Default px suffix on these 5295 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v))) 5296 v += 'px'; 5297 5298 switch (na) { 5299 case 'opacity': 5300 // IE specific opacity 5301 if (isIE) { 5302 s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")"; 5303 5304 if (!n.currentStyle || !n.currentStyle.hasLayout) 5305 s.display = 'inline-block'; 5306 } 5307 5308 // Fix for older browsers 5309 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || ''; 5310 break; 5311 5312 case 'float': 5313 isIE ? s.styleFloat = v : s.cssFloat = v; 5314 break; 5315 5316 default: 5317 s[na] = v || ''; 5318 } 5319 5320 // Force update of the style data 5321 if (t.settings.update_styles) 5322 t.setAttrib(e, 'data-mce-style'); 5323 }); 5324 }, 5325 5326 getStyle : function(n, na, c) { 5327 n = this.get(n); 5328 5329 if (!n) 5330 return; 5331 5332 // Gecko 5333 if (this.doc.defaultView && c) { 5334 // Remove camelcase 5335 na = na.replace(/[A-Z]/g, function(a){ 5336 return '-' + a; 5337 }); 5338 5339 try { 5340 return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na); 5341 } catch (ex) { 5342 // Old safari might fail 5343 return null; 5344 } 5345 } 5346 5347 // Camelcase it, if needed 5348 na = na.replace(/-(\D)/g, function(a, b){ 5349 return b.toUpperCase(); 5350 }); 5351 5352 if (na == 'float') 5353 na = isIE ? 'styleFloat' : 'cssFloat'; 5354 5355 // IE & Opera 5356 if (n.currentStyle && c) 5357 return n.currentStyle[na]; 5358 5359 return n.style ? n.style[na] : undefined; 5360 }, 5361 5362 setStyles : function(e, o) { 5363 var t = this, s = t.settings, ol; 5364 5365 ol = s.update_styles; 5366 s.update_styles = 0; 5367 5368 each(o, function(v, n) { 5369 t.setStyle(e, n, v); 5370 }); 5371 5372 // Update style info 5373 s.update_styles = ol; 5374 if (s.update_styles) 5375 t.setAttrib(e, s.cssText); 5376 }, 5377 5378 removeAllAttribs: function(e) { 5379 return this.run(e, function(e) { 5380 var i, attrs = e.attributes; 5381 for (i = attrs.length - 1; i >= 0; i--) { 5382 e.removeAttributeNode(attrs.item(i)); 5383 } 5384 }); 5385 }, 5386 5387 setAttrib : function(e, n, v) { 5388 var t = this; 5389 5390 // Whats the point 5391 if (!e || !n) 5392 return; 5393 5394 // Strict XML mode 5395 if (t.settings.strict) 5396 n = n.toLowerCase(); 5397 5398 return this.run(e, function(e) { 5399 var s = t.settings; 5400 var originalValue = e.getAttribute(n); 5401 if (v !== null) { 5402 switch (n) { 5403 case "style": 5404 if (!is(v, 'string')) { 5405 each(v, function(v, n) { 5406 t.setStyle(e, n, v); 5407 }); 5408 5409 return; 5410 } 5411 5412 // No mce_style for elements with these since they might get resized by the user 5413 if (s.keep_values) { 5414 if (v && !t._isRes(v)) 5415 e.setAttribute('data-mce-style', v, 2); 5416 else 5417 e.removeAttribute('data-mce-style', 2); 5418 } 5419 5420 e.style.cssText = v; 5421 break; 5422 5423 case "class": 5424 e.className = v || ''; // Fix IE null bug 5425 break; 5426 5427 case "src": 5428 case "href": 5429 if (s.keep_values) { 5430 if (s.url_converter) 5431 v = s.url_converter.call(s.url_converter_scope || t, v, n, e); 5432 5433 t.setAttrib(e, 'data-mce-' + n, v, 2); 5434 } 5435 5436 break; 5437 5438 case "shape": 5439 e.setAttribute('data-mce-style', v); 5440 break; 5441 } 5442 } 5443 if (is(v) && v !== null && v.length !== 0) 5444 e.setAttribute(n, '' + v, 2); 5445 else 5446 e.removeAttribute(n, 2); 5447 5448 // fire onChangeAttrib event for attributes that have changed 5449 if (tinyMCE.activeEditor && originalValue != v) { 5450 var ed = tinyMCE.activeEditor; 5451 ed.onSetAttrib.dispatch(ed, e, n, v); 5452 } 5453 }); 5454 }, 5455 5456 setAttribs : function(e, o) { 5457 var t = this; 5458 5459 return this.run(e, function(e) { 5460 each(o, function(v, n) { 5461 t.setAttrib(e, n, v); 5462 }); 5463 }); 5464 }, 5465 5466 getAttrib : function(e, n, dv) { 5467 var v, t = this, undef; 5468 5469 e = t.get(e); 5470 5471 if (!e || e.nodeType !== 1) 5472 return dv === undef ? false : dv; 5473 5474 if (!is(dv)) 5475 dv = ''; 5476 5477 // Try the mce variant for these 5478 if (/^(src|href|style|coords|shape)$/.test(n)) { 5479 v = e.getAttribute("data-mce-" + n); 5480 5481 if (v) 5482 return v; 5483 } 5484 5485 if (isIE && t.props[n]) { 5486 v = e[t.props[n]]; 5487 v = v && v.nodeValue ? v.nodeValue : v; 5488 } 5489 5490 if (!v) 5491 v = e.getAttribute(n, 2); 5492 5493 // Check boolean attribs 5494 if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) { 5495 if (e[t.props[n]] === true && v === '') 5496 return n; 5497 5498 return v ? n : ''; 5499 } 5500 5501 // Inner input elements will override attributes on form elements 5502 if (e.nodeName === "FORM" && e.getAttributeNode(n)) 5503 return e.getAttributeNode(n).nodeValue; 5504 5505 if (n === 'style') { 5506 v = v || e.style.cssText; 5507 5508 if (v) { 5509 v = t.serializeStyle(t.parseStyle(v), e.nodeName); 5510 5511 if (t.settings.keep_values && !t._isRes(v)) 5512 e.setAttribute('data-mce-style', v); 5513 } 5514 } 5515 5516 // Remove Apple and WebKit stuff 5517 if (isWebKit && n === "class" && v) 5518 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, ''); 5519 5520 // Handle IE issues 5521 if (isIE) { 5522 switch (n) { 5523 case 'rowspan': 5524 case 'colspan': 5525 // IE returns 1 as default value 5526 if (v === 1) 5527 v = ''; 5528 5529 break; 5530 5531 case 'size': 5532 // IE returns +0 as default value for size 5533 if (v === '+0' || v === 20 || v === 0) 5534 v = ''; 5535 5536 break; 5537 5538 case 'width': 5539 case 'height': 5540 case 'vspace': 5541 case 'checked': 5542 case 'disabled': 5543 case 'readonly': 5544 if (v === 0) 5545 v = ''; 5546 5547 break; 5548 5549 case 'hspace': 5550 // IE returns -1 as default value 5551 if (v === -1) 5552 v = ''; 5553 5554 break; 5555 5556 case 'maxlength': 5557 case 'tabindex': 5558 // IE returns default value 5559 if (v === 32768 || v === 2147483647 || v === '32768') 5560 v = ''; 5561 5562 break; 5563 5564 case 'multiple': 5565 case 'compact': 5566 case 'noshade': 5567 case 'nowrap': 5568 if (v === 65535) 5569 return n; 5570 5571 return dv; 5572 5573 case 'shape': 5574 v = v.toLowerCase(); 5575 break; 5576 5577 default: 5578 // IE has odd anonymous function for event attributes 5579 if (n.indexOf('on') === 0 && v) 5580 v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v); 5581 } 5582 } 5583 5584 return (v !== undef && v !== null && v !== '') ? '' + v : dv; 5585 }, 5586 5587 getPos : function(n, ro) { 5588 var t = this, x = 0, y = 0, e, d = t.doc, r; 5589 5590 n = t.get(n); 5591 ro = ro || d.body; 5592 5593 if (n) { 5594 // Use getBoundingClientRect if it exists since it's faster than looping offset nodes 5595 if (n.getBoundingClientRect) { 5596 n = n.getBoundingClientRect(); 5597 e = t.boxModel ? d.documentElement : d.body; 5598 5599 // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit 5600 // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position 5601 x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop; 5602 y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft; 5603 5604 return {x : x, y : y}; 5605 } 5606 5607 r = n; 5608 while (r && r != ro && r.nodeType) { 5609 x += r.offsetLeft || 0; 5610 y += r.offsetTop || 0; 5611 r = r.offsetParent; 5612 } 5613 5614 r = n.parentNode; 5615 while (r && r != ro && r.nodeType) { 5616 x -= r.scrollLeft || 0; 5617 y -= r.scrollTop || 0; 5618 r = r.parentNode; 5619 } 5620 } 5621 5622 return {x : x, y : y}; 5623 }, 5624 5625 parseStyle : function(st) { 5626 return this.styles.parse(st); 5627 }, 5628 5629 serializeStyle : function(o, name) { 5630 return this.styles.serialize(o, name); 5631 }, 5632 5633 addStyle: function(cssText) { 5634 var doc = this.doc, head; 5635 5636 // Create style element if needed 5637 styleElm = doc.getElementById('mceDefaultStyles'); 5638 if (!styleElm) { 5639 styleElm = doc.createElement('style'), 5640 styleElm.id = 'mceDefaultStyles'; 5641 styleElm.type = 'text/css'; 5642 5643 head = doc.getElementsByTagName('head')[0] 5644 if (head.firstChild) { 5645 head.insertBefore(styleElm, head.firstChild); 5646 } else { 5647 head.appendChild(styleElm); 5648 } 5649 } 5650 5651 // Append style data to old or new style element 5652 if (styleElm.styleSheet) { 5653 styleElm.styleSheet.cssText += cssText; 5654 } else { 5655 styleElm.appendChild(doc.createTextNode(cssText)); 5656 } 5657 }, 5658 5659 loadCSS : function(u) { 5660 var t = this, d = t.doc, head; 5661 5662 if (!u) 5663 u = ''; 5664 5665 head = d.getElementsByTagName('head')[0]; 5666 5667 each(u.split(','), function(u) { 5668 var link; 5669 5670 if (t.files[u]) 5671 return; 5672 5673 t.files[u] = true; 5674 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)}); 5675 5676 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug 5677 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading 5678 // It's ugly but it seems to work fine. 5679 if (isIE && d.documentMode && d.recalc) { 5680 link.onload = function() { 5681 if (d.recalc) 5682 d.recalc(); 5683 5684 link.onload = null; 5685 }; 5686 } 5687 5688 head.appendChild(link); 5689 }); 5690 }, 5691 5692 addClass : function(e, c) { 5693 return this.run(e, function(e) { 5694 var o; 5695 5696 if (!c) 5697 return 0; 5698 5699 if (this.hasClass(e, c)) 5700 return e.className; 5701 5702 o = this.removeClass(e, c); 5703 5704 return e.className = (o != '' ? (o + ' ') : '') + c; 5705 }); 5706 }, 5707 5708 removeClass : function(e, c) { 5709 var t = this, re; 5710 5711 return t.run(e, function(e) { 5712 var v; 5713 5714 if (t.hasClass(e, c)) { 5715 if (!re) 5716 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g"); 5717 5718 v = e.className.replace(re, ' '); 5719 v = tinymce.trim(v != ' ' ? v : ''); 5720 5721 e.className = v; 5722 5723 // Empty class attr 5724 if (!v) { 5725 e.removeAttribute('class'); 5726 e.removeAttribute('className'); 5727 } 5728 5729 return v; 5730 } 5731 5732 return e.className; 5733 }); 5734 }, 5735 5736 hasClass : function(n, c) { 5737 n = this.get(n); 5738 5739 if (!n || !c) 5740 return false; 5741 5742 return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1; 5743 }, 5744 5745 show : function(e) { 5746 return this.setStyle(e, 'display', 'block'); 5747 }, 5748 5749 hide : function(e) { 5750 return this.setStyle(e, 'display', 'none'); 5751 }, 5752 5753 isHidden : function(e) { 5754 e = this.get(e); 5755 5756 return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none'; 5757 }, 5758 5759 uniqueId : function(p) { 5760 return (!p ? 'mce_' : p) + (this.counter++); 5761 }, 5762 5763 setHTML : function(element, html) { 5764 var self = this; 5765 5766 return self.run(element, function(element) { 5767 if (isIE) { 5768 // Remove all child nodes, IE keeps empty text nodes in DOM 5769 while (element.firstChild) 5770 element.removeChild(element.firstChild); 5771 5772 try { 5773 // IE will remove comments from the beginning 5774 // unless you padd the contents with something 5775 element.innerHTML = '<br />' + html; 5776 element.removeChild(element.firstChild); 5777 } catch (ex) { 5778 // IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p 5779 // This seems to fix this problem 5780 5781 // Create new div with HTML contents and a BR infront to keep comments 5782 var newElement = self.create('div'); 5783 newElement.innerHTML = '<br />' + html; 5784 5785 // Add all children from div to target 5786 each (tinymce.grep(newElement.childNodes), function(node, i) { 5787 // Skip br element 5788 if (i && element.canHaveHTML) 5789 element.appendChild(node); 5790 }); 5791 } 5792 } else 5793 element.innerHTML = html; 5794 5795 return html; 5796 }); 5797 }, 5798 5799 getOuterHTML : function(elm) { 5800 var doc, self = this; 5801 5802 elm = self.get(elm); 5803 5804 if (!elm) 5805 return null; 5806 5807 if (elm.nodeType === 1 && self.hasOuterHTML) 5808 return elm.outerHTML; 5809 5810 doc = (elm.ownerDocument || self.doc).createElement("body"); 5811 doc.appendChild(elm.cloneNode(true)); 5812 5813 return doc.innerHTML; 5814 }, 5815 5816 setOuterHTML : function(e, h, d) { 5817 var t = this; 5818 5819 function setHTML(e, h, d) { 5820 var n, tp; 5821 5822 tp = d.createElement("body"); 5823 tp.innerHTML = h; 5824 5825 n = tp.lastChild; 5826 while (n) { 5827 t.insertAfter(n.cloneNode(true), e); 5828 n = n.previousSibling; 5829 } 5830 5831 t.remove(e); 5832 }; 5833 5834 return this.run(e, function(e) { 5835 e = t.get(e); 5836 5837 // Only set HTML on elements 5838 if (e.nodeType == 1) { 5839 d = d || e.ownerDocument || t.doc; 5840 5841 if (isIE) { 5842 try { 5843 // Try outerHTML for IE it sometimes produces an unknown runtime error 5844 if (isIE && e.nodeType == 1) 5845 e.outerHTML = h; 5846 else 5847 setHTML(e, h, d); 5848 } catch (ex) { 5849 // Fix for unknown runtime error 5850 setHTML(e, h, d); 5851 } 5852 } else 5853 setHTML(e, h, d); 5854 } 5855 }); 5856 }, 5857 5858 decode : Entities.decode, 5859 5860 encode : Entities.encodeAllRaw, 5861 5862 insertAfter : function(node, reference_node) { 5863 reference_node = this.get(reference_node); 5864 5865 return this.run(node, function(node) { 5866 var parent, nextSibling; 5867 5868 parent = reference_node.parentNode; 5869 nextSibling = reference_node.nextSibling; 5870 5871 if (nextSibling) 5872 parent.insertBefore(node, nextSibling); 5873 else 5874 parent.appendChild(node); 5875 5876 return node; 5877 }); 5878 }, 5879 5880 replace : function(n, o, k) { 5881 var t = this; 5882 5883 if (is(o, 'array')) 5884 n = n.cloneNode(true); 5885 5886 return t.run(o, function(o) { 5887 if (k) { 5888 each(tinymce.grep(o.childNodes), function(c) { 5889 n.appendChild(c); 5890 }); 5891 } 5892 5893 return o.parentNode.replaceChild(n, o); 5894 }); 5895 }, 5896 5897 rename : function(elm, name) { 5898 var t = this, newElm; 5899 5900 if (elm.nodeName != name.toUpperCase()) { 5901 // Rename block element 5902 newElm = t.create(name); 5903 5904 // Copy attribs to new block 5905 each(t.getAttribs(elm), function(attr_node) { 5906 t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName)); 5907 }); 5908 5909 // Replace block 5910 t.replace(newElm, elm, 1); 5911 } 5912 5913 return newElm || elm; 5914 }, 5915 5916 findCommonAncestor : function(a, b) { 5917 var ps = a, pe; 5918 5919 while (ps) { 5920 pe = b; 5921 5922 while (pe && ps != pe) 5923 pe = pe.parentNode; 5924 5925 if (ps == pe) 5926 break; 5927 5928 ps = ps.parentNode; 5929 } 5930 5931 if (!ps && a.ownerDocument) 5932 return a.ownerDocument.documentElement; 5933 5934 return ps; 5935 }, 5936 5937 toHex : function(s) { 5938 var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s); 5939 5940 function hex(s) { 5941 s = parseInt(s, 10).toString(16); 5942 5943 return s.length > 1 ? s : '0' + s; // 0 -> 00 5944 }; 5945 5946 if (c) { 5947 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]); 5948 5949 return s; 5950 } 5951 5952 return s; 5953 }, 5954 5955 getClasses : function() { 5956 var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov; 5957 5958 if (t.classes) 5959 return t.classes; 5960 5961 function addClasses(s) { 5962 // IE style imports 5963 each(s.imports, function(r) { 5964 addClasses(r); 5965 }); 5966 5967 each(s.cssRules || s.rules, function(r) { 5968 // Real type or fake it on IE 5969 switch (r.type || 1) { 5970 // Rule 5971 case 1: 5972 if (r.selectorText) { 5973 each(r.selectorText.split(','), function(v) { 5974 v = v.replace(/^\s*|\s*$|^\s\./g, ""); 5975 5976 // Is internal or it doesn't contain a class 5977 if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v)) 5978 return; 5979 5980 // Remove everything but class name 5981 ov = v; 5982 v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v); 5983 5984 // Filter classes 5985 if (f && !(v = f(v, ov))) 5986 return; 5987 5988 if (!lo[v]) { 5989 cl.push({'class' : v}); 5990 lo[v] = 1; 5991 } 5992 }); 5993 } 5994 break; 5995 5996 // Import 5997 case 3: 5998 addClasses(r.styleSheet); 5999 break; 6000 } 6001 }); 6002 }; 6003 6004 try { 6005 each(t.doc.styleSheets, addClasses); 6006 } catch (ex) { 6007 // Ignore 6008 } 6009 6010 if (cl.length > 0) 6011 t.classes = cl; 6012 6013 return cl; 6014 }, 6015 6016 run : function(e, f, s) { 6017 var t = this, o; 6018 6019 if (t.doc && typeof(e) === 'string') 6020 e = t.get(e); 6021 6022 if (!e) 6023 return false; 6024 6025 s = s || this; 6026 if (!e.nodeType && (e.length || e.length === 0)) { 6027 o = []; 6028 6029 each(e, function(e, i) { 6030 if (e) { 6031 if (typeof(e) == 'string') 6032 e = t.doc.getElementById(e); 6033 6034 o.push(f.call(s, e, i)); 6035 } 6036 }); 6037 6038 return o; 6039 } 6040 6041 return f.call(s, e); 6042 }, 6043 6044 getAttribs : function(n) { 6045 var o; 6046 6047 n = this.get(n); 6048 6049 if (!n) 6050 return []; 6051 6052 if (isIE) { 6053 o = []; 6054 6055 // Object will throw exception in IE 6056 if (n.nodeName == 'OBJECT') 6057 return n.attributes; 6058 6059 // IE doesn't keep the selected attribute if you clone option elements 6060 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected')) 6061 o.push({specified : 1, nodeName : 'selected'}); 6062 6063 // It's crazy that this is faster in IE but it's because it returns all attributes all the time 6064 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) { 6065 o.push({specified : 1, nodeName : a}); 6066 }); 6067 6068 return o; 6069 } 6070 6071 return n.attributes; 6072 }, 6073 6074 isEmpty : function(node, elements) { 6075 var self = this, i, attributes, type, walker, name, brCount = 0; 6076 6077 node = node.firstChild; 6078 if (node) { 6079 walker = new tinymce.dom.TreeWalker(node, node.parentNode); 6080 elements = elements || self.schema ? self.schema.getNonEmptyElements() : null; 6081 6082 do { 6083 type = node.nodeType; 6084 6085 if (type === 1) { 6086 // Ignore bogus elements 6087 if (node.getAttribute('data-mce-bogus')) 6088 continue; 6089 6090 // Keep empty elements like <img /> 6091 name = node.nodeName.toLowerCase(); 6092 if (elements && elements[name]) { 6093 // Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p> 6094 if (name === 'br') { 6095 brCount++; 6096 continue; 6097 } 6098 6099 return false; 6100 } 6101 6102 // Keep elements with data-bookmark attributes or name attribute like <a name="1"></a> 6103 attributes = self.getAttribs(node); 6104 i = node.attributes.length; 6105 while (i--) { 6106 name = node.attributes[i].nodeName; 6107 if (name === "name" || name === 'data-mce-bookmark') 6108 return false; 6109 } 6110 } 6111 6112 // Keep comment nodes 6113 if (type == 8) 6114 return false; 6115 6116 // Keep non whitespace text nodes 6117 if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue))) 6118 return false; 6119 } while (node = walker.next()); 6120 } 6121 6122 return brCount <= 1; 6123 }, 6124 6125 destroy : function(s) { 6126 var t = this; 6127 6128 t.win = t.doc = t.root = t.events = t.frag = null; 6129 6130 // Manual destroy then remove unload handler 6131 if (!s) 6132 tinymce.removeUnload(t.destroy); 6133 }, 6134 6135 createRng : function() { 6136 var d = this.doc; 6137 6138 return d.createRange ? d.createRange() : new tinymce.dom.Range(this); 6139 }, 6140 6141 nodeIndex : function(node, normalized) { 6142 var idx = 0, lastNodeType, lastNode, nodeType; 6143 6144 if (node) { 6145 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) { 6146 nodeType = node.nodeType; 6147 6148 // Normalize text nodes 6149 if (normalized && nodeType == 3) { 6150 if (nodeType == lastNodeType || !node.nodeValue.length) 6151 continue; 6152 } 6153 idx++; 6154 lastNodeType = nodeType; 6155 } 6156 } 6157 6158 return idx; 6159 }, 6160 6161 split : function(pe, e, re) { 6162 var t = this, r = t.createRng(), bef, aft, pa; 6163 6164 // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense 6165 // but we don't want that in our code since it serves no purpose for the end user 6166 // For example if this is chopped: 6167 // <p>text 1<span><b>CHOP</b></span>text 2</p> 6168 // would produce: 6169 // <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p> 6170 // this function will then trim of empty edges and produce: 6171 // <p>text 1</p><b>CHOP</b><p>text 2</p> 6172 function trim(node) { 6173 var i, children = node.childNodes, type = node.nodeType; 6174 6175 function surroundedBySpans(node) { 6176 var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN'; 6177 var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN'; 6178 return previousIsSpan && nextIsSpan; 6179 } 6180 6181 if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark') 6182 return; 6183 6184 for (i = children.length - 1; i >= 0; i--) 6185 trim(children[i]); 6186 6187 if (type != 9) { 6188 // Keep non whitespace text nodes 6189 if (type == 3 && node.nodeValue.length > 0) { 6190 // If parent element isn't a block or there isn't any useful contents for example "<p> </p>" 6191 // Also keep text nodes with only spaces if surrounded by spans. 6192 // eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b 6193 var trimmedLength = tinymce.trim(node.nodeValue).length; 6194 if (!t.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node)) 6195 return; 6196 } else if (type == 1) { 6197 // If the only child is a bookmark then move it up 6198 children = node.childNodes; 6199 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark') 6200 node.parentNode.insertBefore(children[0], node); 6201 6202 // Keep non empty elements or img, hr etc 6203 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName)) 6204 return; 6205 } 6206 6207 t.remove(node); 6208 } 6209 6210 return node; 6211 }; 6212 6213 if (pe && e) { 6214 // Get before chunk 6215 r.setStart(pe.parentNode, t.nodeIndex(pe)); 6216 r.setEnd(e.parentNode, t.nodeIndex(e)); 6217 bef = r.extractContents(); 6218 6219 // Get after chunk 6220 r = t.createRng(); 6221 r.setStart(e.parentNode, t.nodeIndex(e) + 1); 6222 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1); 6223 aft = r.extractContents(); 6224 6225 // Insert before chunk 6226 pa = pe.parentNode; 6227 pa.insertBefore(trim(bef), pe); 6228 6229 // Insert middle chunk 6230 if (re) 6231 pa.replaceChild(re, e); 6232 else 6233 pa.insertBefore(e, pe); 6234 6235 // Insert after chunk 6236 pa.insertBefore(trim(aft), pe); 6237 t.remove(pe); 6238 6239 return re || e; 6240 } 6241 }, 6242 6243 bind : function(target, name, func, scope) { 6244 return this.events.add(target, name, func, scope || this); 6245 }, 6246 6247 unbind : function(target, name, func) { 6248 return this.events.remove(target, name, func); 6249 }, 6250 6251 fire : function(target, name, evt) { 6252 return this.events.fire(target, name, evt); 6253 }, 6254 6255 // Returns the content editable state of a node 6256 getContentEditable: function(node) { 6257 var contentEditable; 6258 6259 // Check type 6260 if (node.nodeType != 1) { 6261 return null; 6262 } 6263 6264 // Check for fake content editable 6265 contentEditable = node.getAttribute("data-mce-contenteditable"); 6266 if (contentEditable && contentEditable !== "inherit") { 6267 return contentEditable; 6268 } 6269 6270 // Check for real content editable 6271 return node.contentEditable !== "inherit" ? node.contentEditable : null; 6272 }, 6273 6274 6275 _findSib : function(node, selector, name) { 6276 var t = this, f = selector; 6277 6278 if (node) { 6279 // If expression make a function of it using is 6280 if (is(f, 'string')) { 6281 f = function(node) { 6282 return t.is(node, selector); 6283 }; 6284 } 6285 6286 // Loop all siblings 6287 for (node = node[name]; node; node = node[name]) { 6288 if (f(node)) 6289 return node; 6290 } 6291 } 6292 6293 return null; 6294 }, 6295 6296 _isRes : function(c) { 6297 // Is live resizble element 6298 return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c); 6299 } 6300 6301 /* 6302 walk : function(n, f, s) { 6303 var d = this.doc, w; 6304 6305 if (d.createTreeWalker) { 6306 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); 6307 6308 while ((n = w.nextNode()) != null) 6309 f.call(s || this, n); 6310 } else 6311 tinymce.walk(n, f, 'childNodes', s); 6312 } 6313 */ 6314 6315 /* 6316 toRGB : function(s) { 6317 var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s); 6318 6319 if (c) { 6320 // #FFF -> #FFFFFF 6321 if (!is(c[3])) 6322 c[3] = c[2] = c[1]; 6323 6324 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")"; 6325 } 6326 6327 return s; 6328 } 6329 */ 6330 }); 6331 6332 tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0}); 6333 })(tinymce); 6334 6335 (function(ns) { 6336 // Range constructor 6337 function Range(dom) { 6338 var t = this, 6339 doc = dom.doc, 6340 EXTRACT = 0, 6341 CLONE = 1, 6342 DELETE = 2, 6343 TRUE = true, 6344 FALSE = false, 6345 START_OFFSET = 'startOffset', 6346 START_CONTAINER = 'startContainer', 6347 END_CONTAINER = 'endContainer', 6348 END_OFFSET = 'endOffset', 6349 extend = tinymce.extend, 6350 nodeIndex = dom.nodeIndex; 6351 6352 extend(t, { 6353 // Inital states 6354 startContainer : doc, 6355 startOffset : 0, 6356 endContainer : doc, 6357 endOffset : 0, 6358 collapsed : TRUE, 6359 commonAncestorContainer : doc, 6360 6361 // Range constants 6362 START_TO_START : 0, 6363 START_TO_END : 1, 6364 END_TO_END : 2, 6365 END_TO_START : 3, 6366 6367 // Public methods 6368 setStart : setStart, 6369 setEnd : setEnd, 6370 setStartBefore : setStartBefore, 6371 setStartAfter : setStartAfter, 6372 setEndBefore : setEndBefore, 6373 setEndAfter : setEndAfter, 6374 collapse : collapse, 6375 selectNode : selectNode, 6376 selectNodeContents : selectNodeContents, 6377 compareBoundaryPoints : compareBoundaryPoints, 6378 deleteContents : deleteContents, 6379 extractContents : extractContents, 6380 cloneContents : cloneContents, 6381 insertNode : insertNode, 6382 surroundContents : surroundContents, 6383 cloneRange : cloneRange, 6384 toStringIE : toStringIE 6385 }); 6386 6387 function createDocumentFragment() { 6388 return doc.createDocumentFragment(); 6389 }; 6390 6391 function setStart(n, o) { 6392 _setEndPoint(TRUE, n, o); 6393 }; 6394 6395 function setEnd(n, o) { 6396 _setEndPoint(FALSE, n, o); 6397 }; 6398 6399 function setStartBefore(n) { 6400 setStart(n.parentNode, nodeIndex(n)); 6401 }; 6402 6403 function setStartAfter(n) { 6404 setStart(n.parentNode, nodeIndex(n) + 1); 6405 }; 6406 6407 function setEndBefore(n) { 6408 setEnd(n.parentNode, nodeIndex(n)); 6409 }; 6410 6411 function setEndAfter(n) { 6412 setEnd(n.parentNode, nodeIndex(n) + 1); 6413 }; 6414 6415 function collapse(ts) { 6416 if (ts) { 6417 t[END_CONTAINER] = t[START_CONTAINER]; 6418 t[END_OFFSET] = t[START_OFFSET]; 6419 } else { 6420 t[START_CONTAINER] = t[END_CONTAINER]; 6421 t[START_OFFSET] = t[END_OFFSET]; 6422 } 6423 6424 t.collapsed = TRUE; 6425 }; 6426 6427 function selectNode(n) { 6428 setStartBefore(n); 6429 setEndAfter(n); 6430 }; 6431 6432 function selectNodeContents(n) { 6433 setStart(n, 0); 6434 setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length); 6435 }; 6436 6437 function compareBoundaryPoints(h, r) { 6438 var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET], 6439 rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset; 6440 6441 // Check START_TO_START 6442 if (h === 0) 6443 return _compareBoundaryPoints(sc, so, rsc, rso); 6444 6445 // Check START_TO_END 6446 if (h === 1) 6447 return _compareBoundaryPoints(ec, eo, rsc, rso); 6448 6449 // Check END_TO_END 6450 if (h === 2) 6451 return _compareBoundaryPoints(ec, eo, rec, reo); 6452 6453 // Check END_TO_START 6454 if (h === 3) 6455 return _compareBoundaryPoints(sc, so, rec, reo); 6456 }; 6457 6458 function deleteContents() { 6459 _traverse(DELETE); 6460 }; 6461 6462 function extractContents() { 6463 return _traverse(EXTRACT); 6464 }; 6465 6466 function cloneContents() { 6467 return _traverse(CLONE); 6468 }; 6469 6470 function insertNode(n) { 6471 var startContainer = this[START_CONTAINER], 6472 startOffset = this[START_OFFSET], nn, o; 6473 6474 // Node is TEXT_NODE or CDATA 6475 if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) { 6476 if (!startOffset) { 6477 // At the start of text 6478 startContainer.parentNode.insertBefore(n, startContainer); 6479 } else if (startOffset >= startContainer.nodeValue.length) { 6480 // At the end of text 6481 dom.insertAfter(n, startContainer); 6482 } else { 6483 // Middle, need to split 6484 nn = startContainer.splitText(startOffset); 6485 startContainer.parentNode.insertBefore(n, nn); 6486 } 6487 } else { 6488 // Insert element node 6489 if (startContainer.childNodes.length > 0) 6490 o = startContainer.childNodes[startOffset]; 6491 6492 if (o) 6493 startContainer.insertBefore(n, o); 6494 else 6495 startContainer.appendChild(n); 6496 } 6497 }; 6498 6499 function surroundContents(n) { 6500 var f = t.extractContents(); 6501 6502 t.insertNode(n); 6503 n.appendChild(f); 6504 t.selectNode(n); 6505 }; 6506 6507 function cloneRange() { 6508 return extend(new Range(dom), { 6509 startContainer : t[START_CONTAINER], 6510 startOffset : t[START_OFFSET], 6511 endContainer : t[END_CONTAINER], 6512 endOffset : t[END_OFFSET], 6513 collapsed : t.collapsed, 6514 commonAncestorContainer : t.commonAncestorContainer 6515 }); 6516 }; 6517 6518 // Private methods 6519 6520 function _getSelectedNode(container, offset) { 6521 var child; 6522 6523 if (container.nodeType == 3 /* TEXT_NODE */) 6524 return container; 6525 6526 if (offset < 0) 6527 return container; 6528 6529 child = container.firstChild; 6530 while (child && offset > 0) { 6531 --offset; 6532 child = child.nextSibling; 6533 } 6534 6535 if (child) 6536 return child; 6537 6538 return container; 6539 }; 6540 6541 function _isCollapsed() { 6542 return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]); 6543 }; 6544 6545 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) { 6546 var c, offsetC, n, cmnRoot, childA, childB; 6547 6548 // In the first case the boundary-points have the same container. A is before B 6549 // if its offset is less than the offset of B, A is equal to B if its offset is 6550 // equal to the offset of B, and A is after B if its offset is greater than the 6551 // offset of B. 6552 if (containerA == containerB) { 6553 if (offsetA == offsetB) 6554 return 0; // equal 6555 6556 if (offsetA < offsetB) 6557 return -1; // before 6558 6559 return 1; // after 6560 } 6561 6562 // In the second case a child node C of the container of A is an ancestor 6563 // container of B. In this case, A is before B if the offset of A is less than or 6564 // equal to the index of the child node C and A is after B otherwise. 6565 c = containerB; 6566 while (c && c.parentNode != containerA) 6567 c = c.parentNode; 6568 6569 if (c) { 6570 offsetC = 0; 6571 n = containerA.firstChild; 6572 6573 while (n != c && offsetC < offsetA) { 6574 offsetC++; 6575 n = n.nextSibling; 6576 } 6577 6578 if (offsetA <= offsetC) 6579 return -1; // before 6580 6581 return 1; // after 6582 } 6583 6584 // In the third case a child node C of the container of B is an ancestor container 6585 // of A. In this case, A is before B if the index of the child node C is less than 6586 // the offset of B and A is after B otherwise. 6587 c = containerA; 6588 while (c && c.parentNode != containerB) { 6589 c = c.parentNode; 6590 } 6591 6592 if (c) { 6593 offsetC = 0; 6594 n = containerB.firstChild; 6595 6596 while (n != c && offsetC < offsetB) { 6597 offsetC++; 6598 n = n.nextSibling; 6599 } 6600 6601 if (offsetC < offsetB) 6602 return -1; // before 6603 6604 return 1; // after 6605 } 6606 6607 // In the fourth case, none of three other cases hold: the containers of A and B 6608 // are siblings or descendants of sibling nodes. In this case, A is before B if 6609 // the container of A is before the container of B in a pre-order traversal of the 6610 // Ranges' context tree and A is after B otherwise. 6611 cmnRoot = dom.findCommonAncestor(containerA, containerB); 6612 childA = containerA; 6613 6614 while (childA && childA.parentNode != cmnRoot) 6615 childA = childA.parentNode; 6616 6617 if (!childA) 6618 childA = cmnRoot; 6619 6620 childB = containerB; 6621 while (childB && childB.parentNode != cmnRoot) 6622 childB = childB.parentNode; 6623 6624 if (!childB) 6625 childB = cmnRoot; 6626 6627 if (childA == childB) 6628 return 0; // equal 6629 6630 n = cmnRoot.firstChild; 6631 while (n) { 6632 if (n == childA) 6633 return -1; // before 6634 6635 if (n == childB) 6636 return 1; // after 6637 6638 n = n.nextSibling; 6639 } 6640 }; 6641 6642 function _setEndPoint(st, n, o) { 6643 var ec, sc; 6644 6645 if (st) { 6646 t[START_CONTAINER] = n; 6647 t[START_OFFSET] = o; 6648 } else { 6649 t[END_CONTAINER] = n; 6650 t[END_OFFSET] = o; 6651 } 6652 6653 // If one boundary-point of a Range is set to have a root container 6654 // other than the current one for the Range, the Range is collapsed to 6655 // the new position. This enforces the restriction that both boundary- 6656 // points of a Range must have the same root container. 6657 ec = t[END_CONTAINER]; 6658 while (ec.parentNode) 6659 ec = ec.parentNode; 6660 6661 sc = t[START_CONTAINER]; 6662 while (sc.parentNode) 6663 sc = sc.parentNode; 6664 6665 if (sc == ec) { 6666 // The start position of a Range is guaranteed to never be after the 6667 // end position. To enforce this restriction, if the start is set to 6668 // be at a position after the end, the Range is collapsed to that 6669 // position. 6670 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0) 6671 t.collapse(st); 6672 } else 6673 t.collapse(st); 6674 6675 t.collapsed = _isCollapsed(); 6676 t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]); 6677 }; 6678 6679 function _traverse(how) { 6680 var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep; 6681 6682 if (t[START_CONTAINER] == t[END_CONTAINER]) 6683 return _traverseSameContainer(how); 6684 6685 for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { 6686 if (p == t[START_CONTAINER]) 6687 return _traverseCommonStartContainer(c, how); 6688 6689 ++endContainerDepth; 6690 } 6691 6692 for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { 6693 if (p == t[END_CONTAINER]) 6694 return _traverseCommonEndContainer(c, how); 6695 6696 ++startContainerDepth; 6697 } 6698 6699 depthDiff = startContainerDepth - endContainerDepth; 6700 6701 startNode = t[START_CONTAINER]; 6702 while (depthDiff > 0) { 6703 startNode = startNode.parentNode; 6704 depthDiff--; 6705 } 6706 6707 endNode = t[END_CONTAINER]; 6708 while (depthDiff < 0) { 6709 endNode = endNode.parentNode; 6710 depthDiff++; 6711 } 6712 6713 // ascend the ancestor hierarchy until we have a common parent. 6714 for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) { 6715 startNode = sp; 6716 endNode = ep; 6717 } 6718 6719 return _traverseCommonAncestors(startNode, endNode, how); 6720 }; 6721 6722 function _traverseSameContainer(how) { 6723 var frag, s, sub, n, cnt, sibling, xferNode, start, len; 6724 6725 if (how != DELETE) 6726 frag = createDocumentFragment(); 6727 6728 // If selection is empty, just return the fragment 6729 if (t[START_OFFSET] == t[END_OFFSET]) 6730 return frag; 6731 6732 // Text node needs special case handling 6733 if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) { 6734 // get the substring 6735 s = t[START_CONTAINER].nodeValue; 6736 sub = s.substring(t[START_OFFSET], t[END_OFFSET]); 6737 6738 // set the original text node to its new value 6739 if (how != CLONE) { 6740 n = t[START_CONTAINER]; 6741 start = t[START_OFFSET]; 6742 len = t[END_OFFSET] - t[START_OFFSET]; 6743 6744 if (start === 0 && len >= n.nodeValue.length - 1) { 6745 n.parentNode.removeChild(n); 6746 } else { 6747 n.deleteData(start, len); 6748 } 6749 6750 // Nothing is partially selected, so collapse to start point 6751 t.collapse(TRUE); 6752 } 6753 6754 if (how == DELETE) 6755 return; 6756 6757 if (sub.length > 0) { 6758 frag.appendChild(doc.createTextNode(sub)); 6759 } 6760 6761 return frag; 6762 } 6763 6764 // Copy nodes between the start/end offsets. 6765 n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]); 6766 cnt = t[END_OFFSET] - t[START_OFFSET]; 6767 6768 while (n && cnt > 0) { 6769 sibling = n.nextSibling; 6770 xferNode = _traverseFullySelected(n, how); 6771 6772 if (frag) 6773 frag.appendChild( xferNode ); 6774 6775 --cnt; 6776 n = sibling; 6777 } 6778 6779 // Nothing is partially selected, so collapse to start point 6780 if (how != CLONE) 6781 t.collapse(TRUE); 6782 6783 return frag; 6784 }; 6785 6786 function _traverseCommonStartContainer(endAncestor, how) { 6787 var frag, n, endIdx, cnt, sibling, xferNode; 6788 6789 if (how != DELETE) 6790 frag = createDocumentFragment(); 6791 6792 n = _traverseRightBoundary(endAncestor, how); 6793 6794 if (frag) 6795 frag.appendChild(n); 6796 6797 endIdx = nodeIndex(endAncestor); 6798 cnt = endIdx - t[START_OFFSET]; 6799 6800 if (cnt <= 0) { 6801 // Collapse to just before the endAncestor, which 6802 // is partially selected. 6803 if (how != CLONE) { 6804 t.setEndBefore(endAncestor); 6805 t.collapse(FALSE); 6806 } 6807 6808 return frag; 6809 } 6810 6811 n = endAncestor.previousSibling; 6812 while (cnt > 0) { 6813 sibling = n.previousSibling; 6814 xferNode = _traverseFullySelected(n, how); 6815 6816 if (frag) 6817 frag.insertBefore(xferNode, frag.firstChild); 6818 6819 --cnt; 6820 n = sibling; 6821 } 6822 6823 // Collapse to just before the endAncestor, which 6824 // is partially selected. 6825 if (how != CLONE) { 6826 t.setEndBefore(endAncestor); 6827 t.collapse(FALSE); 6828 } 6829 6830 return frag; 6831 }; 6832 6833 function _traverseCommonEndContainer(startAncestor, how) { 6834 var frag, startIdx, n, cnt, sibling, xferNode; 6835 6836 if (how != DELETE) 6837 frag = createDocumentFragment(); 6838 6839 n = _traverseLeftBoundary(startAncestor, how); 6840 if (frag) 6841 frag.appendChild(n); 6842 6843 startIdx = nodeIndex(startAncestor); 6844 ++startIdx; // Because we already traversed it 6845 6846 cnt = t[END_OFFSET] - startIdx; 6847 n = startAncestor.nextSibling; 6848 while (n && cnt > 0) { 6849 sibling = n.nextSibling; 6850 xferNode = _traverseFullySelected(n, how); 6851 6852 if (frag) 6853 frag.appendChild(xferNode); 6854 6855 --cnt; 6856 n = sibling; 6857 } 6858 6859 if (how != CLONE) { 6860 t.setStartAfter(startAncestor); 6861 t.collapse(TRUE); 6862 } 6863 6864 return frag; 6865 }; 6866 6867 function _traverseCommonAncestors(startAncestor, endAncestor, how) { 6868 var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling; 6869 6870 if (how != DELETE) 6871 frag = createDocumentFragment(); 6872 6873 n = _traverseLeftBoundary(startAncestor, how); 6874 if (frag) 6875 frag.appendChild(n); 6876 6877 commonParent = startAncestor.parentNode; 6878 startOffset = nodeIndex(startAncestor); 6879 endOffset = nodeIndex(endAncestor); 6880 ++startOffset; 6881 6882 cnt = endOffset - startOffset; 6883 sibling = startAncestor.nextSibling; 6884 6885 while (cnt > 0) { 6886 nextSibling = sibling.nextSibling; 6887 n = _traverseFullySelected(sibling, how); 6888 6889 if (frag) 6890 frag.appendChild(n); 6891 6892 sibling = nextSibling; 6893 --cnt; 6894 } 6895 6896 n = _traverseRightBoundary(endAncestor, how); 6897 6898 if (frag) 6899 frag.appendChild(n); 6900 6901 if (how != CLONE) { 6902 t.setStartAfter(startAncestor); 6903 t.collapse(TRUE); 6904 } 6905 6906 return frag; 6907 }; 6908 6909 function _traverseRightBoundary(root, how) { 6910 var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER]; 6911 6912 if (next == root) 6913 return _traverseNode(next, isFullySelected, FALSE, how); 6914 6915 parent = next.parentNode; 6916 clonedParent = _traverseNode(parent, FALSE, FALSE, how); 6917 6918 while (parent) { 6919 while (next) { 6920 prevSibling = next.previousSibling; 6921 clonedChild = _traverseNode(next, isFullySelected, FALSE, how); 6922 6923 if (how != DELETE) 6924 clonedParent.insertBefore(clonedChild, clonedParent.firstChild); 6925 6926 isFullySelected = TRUE; 6927 next = prevSibling; 6928 } 6929 6930 if (parent == root) 6931 return clonedParent; 6932 6933 next = parent.previousSibling; 6934 parent = parent.parentNode; 6935 6936 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how); 6937 6938 if (how != DELETE) 6939 clonedGrandParent.appendChild(clonedParent); 6940 6941 clonedParent = clonedGrandParent; 6942 } 6943 }; 6944 6945 function _traverseLeftBoundary(root, how) { 6946 var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent; 6947 6948 if (next == root) 6949 return _traverseNode(next, isFullySelected, TRUE, how); 6950 6951 parent = next.parentNode; 6952 clonedParent = _traverseNode(parent, FALSE, TRUE, how); 6953 6954 while (parent) { 6955 while (next) { 6956 nextSibling = next.nextSibling; 6957 clonedChild = _traverseNode(next, isFullySelected, TRUE, how); 6958 6959 if (how != DELETE) 6960 clonedParent.appendChild(clonedChild); 6961 6962 isFullySelected = TRUE; 6963 next = nextSibling; 6964 } 6965 6966 if (parent == root) 6967 return clonedParent; 6968 6969 next = parent.nextSibling; 6970 parent = parent.parentNode; 6971 6972 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how); 6973 6974 if (how != DELETE) 6975 clonedGrandParent.appendChild(clonedParent); 6976 6977 clonedParent = clonedGrandParent; 6978 } 6979 }; 6980 6981 function _traverseNode(n, isFullySelected, isLeft, how) { 6982 var txtValue, newNodeValue, oldNodeValue, offset, newNode; 6983 6984 if (isFullySelected) 6985 return _traverseFullySelected(n, how); 6986 6987 if (n.nodeType == 3 /* TEXT_NODE */) { 6988 txtValue = n.nodeValue; 6989 6990 if (isLeft) { 6991 offset = t[START_OFFSET]; 6992 newNodeValue = txtValue.substring(offset); 6993 oldNodeValue = txtValue.substring(0, offset); 6994 } else { 6995 offset = t[END_OFFSET]; 6996 newNodeValue = txtValue.substring(0, offset); 6997 oldNodeValue = txtValue.substring(offset); 6998 } 6999 7000 if (how != CLONE) 7001 n.nodeValue = oldNodeValue; 7002 7003 if (how == DELETE) 7004 return; 7005 7006 newNode = dom.clone(n, FALSE); 7007 newNode.nodeValue = newNodeValue; 7008 7009 return newNode; 7010 } 7011 7012 if (how == DELETE) 7013 return; 7014 7015 return dom.clone(n, FALSE); 7016 }; 7017 7018 function _traverseFullySelected(n, how) { 7019 if (how != DELETE) 7020 return how == CLONE ? dom.clone(n, TRUE) : n; 7021 7022 n.parentNode.removeChild(n); 7023 }; 7024 7025 function toStringIE() { 7026 return dom.create('body', null, cloneContents()).outerText; 7027 } 7028 7029 return t; 7030 }; 7031 7032 ns.Range = Range; 7033 7034 // Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype 7035 Range.prototype.toString = function() { 7036 return this.toStringIE(); 7037 }; 7038 })(tinymce.dom); 7039 7040 (function() { 7041 function Selection(selection) { 7042 var self = this, dom = selection.dom, TRUE = true, FALSE = false; 7043 7044 function getPosition(rng, start) { 7045 var checkRng, startIndex = 0, endIndex, inside, 7046 children, child, offset, index, position = -1, parent; 7047 7048 // Setup test range, collapse it and get the parent 7049 checkRng = rng.duplicate(); 7050 checkRng.collapse(start); 7051 parent = checkRng.parentElement(); 7052 7053 // Check if the selection is within the right document 7054 if (parent.ownerDocument !== selection.dom.doc) 7055 return; 7056 7057 // IE will report non editable elements as it's parent so look for an editable one 7058 while (parent.contentEditable === "false") { 7059 parent = parent.parentNode; 7060 } 7061 7062 // If parent doesn't have any children then return that we are inside the element 7063 if (!parent.hasChildNodes()) { 7064 return {node : parent, inside : 1}; 7065 } 7066 7067 // Setup node list and endIndex 7068 children = parent.children; 7069 endIndex = children.length - 1; 7070 7071 // Perform a binary search for the position 7072 while (startIndex <= endIndex) { 7073 index = Math.floor((startIndex + endIndex) / 2); 7074 7075 // Move selection to node and compare the ranges 7076 child = children[index]; 7077 checkRng.moveToElementText(child); 7078 position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng); 7079 7080 // Before/after or an exact match 7081 if (position > 0) { 7082 endIndex = index - 1; 7083 } else if (position < 0) { 7084 startIndex = index + 1; 7085 } else { 7086 return {node : child}; 7087 } 7088 } 7089 7090 // Check if child position is before or we didn't find a position 7091 if (position < 0) { 7092 // No element child was found use the parent element and the offset inside that 7093 if (!child) { 7094 checkRng.moveToElementText(parent); 7095 checkRng.collapse(true); 7096 child = parent; 7097 inside = true; 7098 } else 7099 checkRng.collapse(false); 7100 7101 // Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one 7102 // We need to walk char by char since rng.text or rng.htmlText will trim line endings 7103 offset = 0; 7104 while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) { 7105 if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) { 7106 break; 7107 } 7108 7109 offset++; 7110 } 7111 } else { 7112 // Child position is after the selection endpoint 7113 checkRng.collapse(true); 7114 7115 // Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one 7116 offset = 0; 7117 while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) { 7118 if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) { 7119 break; 7120 } 7121 7122 offset++; 7123 } 7124 } 7125 7126 return {node : child, position : position, offset : offset, inside : inside}; 7127 }; 7128 7129 // Returns a W3C DOM compatible range object by using the IE Range API 7130 function getRange() { 7131 var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail; 7132 7133 // If selection is outside the current document just return an empty range 7134 element = ieRange.item ? ieRange.item(0) : ieRange.parentElement(); 7135 if (element.ownerDocument != dom.doc) 7136 return domRange; 7137 7138 collapsed = selection.isCollapsed(); 7139 7140 // Handle control selection 7141 if (ieRange.item) { 7142 domRange.setStart(element.parentNode, dom.nodeIndex(element)); 7143 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1); 7144 7145 return domRange; 7146 } 7147 7148 function findEndPoint(start) { 7149 var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue; 7150 7151 container = endPoint.node; 7152 offset = endPoint.offset; 7153 7154 if (endPoint.inside && !container.hasChildNodes()) { 7155 domRange[start ? 'setStart' : 'setEnd'](container, 0); 7156 return; 7157 } 7158 7159 if (offset === undef) { 7160 domRange[start ? 'setStartBefore' : 'setEndAfter'](container); 7161 return; 7162 } 7163 7164 if (endPoint.position < 0) { 7165 sibling = endPoint.inside ? container.firstChild : container.nextSibling; 7166 7167 if (!sibling) { 7168 domRange[start ? 'setStartAfter' : 'setEndAfter'](container); 7169 return; 7170 } 7171 7172 if (!offset) { 7173 if (sibling.nodeType == 3) 7174 domRange[start ? 'setStart' : 'setEnd'](sibling, 0); 7175 else 7176 domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling); 7177 7178 return; 7179 } 7180 7181 // Find the text node and offset 7182 while (sibling) { 7183 nodeValue = sibling.nodeValue; 7184 textNodeOffset += nodeValue.length; 7185 7186 // We are at or passed the position we where looking for 7187 if (textNodeOffset >= offset) { 7188 container = sibling; 7189 textNodeOffset -= offset; 7190 textNodeOffset = nodeValue.length - textNodeOffset; 7191 break; 7192 } 7193 7194 sibling = sibling.nextSibling; 7195 } 7196 } else { 7197 // Find the text node and offset 7198 sibling = container.previousSibling; 7199 7200 if (!sibling) 7201 return domRange[start ? 'setStartBefore' : 'setEndBefore'](container); 7202 7203 // If there isn't any text to loop then use the first position 7204 if (!offset) { 7205 if (container.nodeType == 3) 7206 domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length); 7207 else 7208 domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling); 7209 7210 return; 7211 } 7212 7213 while (sibling) { 7214 textNodeOffset += sibling.nodeValue.length; 7215 7216 // We are at or passed the position we where looking for 7217 if (textNodeOffset >= offset) { 7218 container = sibling; 7219 textNodeOffset -= offset; 7220 break; 7221 } 7222 7223 sibling = sibling.previousSibling; 7224 } 7225 } 7226 7227 domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset); 7228 }; 7229 7230 try { 7231 // Find start point 7232 findEndPoint(true); 7233 7234 // Find end point if needed 7235 if (!collapsed) 7236 findEndPoint(); 7237 } catch (ex) { 7238 // IE has a nasty bug where text nodes might throw "invalid argument" when you 7239 // access the nodeValue or other properties of text nodes. This seems to happend when 7240 // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it. 7241 if (ex.number == -2147024809) { 7242 // Get the current selection 7243 bookmark = self.getBookmark(2); 7244 7245 // Get start element 7246 tmpRange = ieRange.duplicate(); 7247 tmpRange.collapse(true); 7248 element = tmpRange.parentElement(); 7249 7250 // Get end element 7251 if (!collapsed) { 7252 tmpRange = ieRange.duplicate(); 7253 tmpRange.collapse(false); 7254 element2 = tmpRange.parentElement(); 7255 element2.innerHTML = element2.innerHTML; 7256 } 7257 7258 // Remove the broken elements 7259 element.innerHTML = element.innerHTML; 7260 7261 // Restore the selection 7262 self.moveToBookmark(bookmark); 7263 7264 // Since the range has moved we need to re-get it 7265 ieRange = selection.getRng(); 7266 7267 // Find start point 7268 findEndPoint(true); 7269 7270 // Find end point if needed 7271 if (!collapsed) 7272 findEndPoint(); 7273 } else 7274 throw ex; // Throw other errors 7275 } 7276 7277 return domRange; 7278 }; 7279 7280 this.getBookmark = function(type) { 7281 var rng = selection.getRng(), start, end, bookmark = {}; 7282 7283 function getIndexes(node) { 7284 var parent, root, children, i, indexes = []; 7285 7286 parent = node.parentNode; 7287 root = dom.getRoot().parentNode; 7288 7289 while (parent != root && parent.nodeType !== 9) { 7290 children = parent.children; 7291 7292 i = children.length; 7293 while (i--) { 7294 if (node === children[i]) { 7295 indexes.push(i); 7296 break; 7297 } 7298 } 7299 7300 node = parent; 7301 parent = parent.parentNode; 7302 } 7303 7304 return indexes; 7305 }; 7306 7307 function getBookmarkEndPoint(start) { 7308 var position; 7309 7310 position = getPosition(rng, start); 7311 if (position) { 7312 return { 7313 position : position.position, 7314 offset : position.offset, 7315 indexes : getIndexes(position.node), 7316 inside : position.inside 7317 }; 7318 } 7319 }; 7320 7321 // Non ubstructive bookmark 7322 if (type === 2) { 7323 // Handle text selection 7324 if (!rng.item) { 7325 bookmark.start = getBookmarkEndPoint(true); 7326 7327 if (!selection.isCollapsed()) 7328 bookmark.end = getBookmarkEndPoint(); 7329 } else 7330 bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))}; 7331 } 7332 7333 return bookmark; 7334 }; 7335 7336 this.moveToBookmark = function(bookmark) { 7337 var rng, body = dom.doc.body; 7338 7339 function resolveIndexes(indexes) { 7340 var node, i, idx, children; 7341 7342 node = dom.getRoot(); 7343 for (i = indexes.length - 1; i >= 0; i--) { 7344 children = node.children; 7345 idx = indexes[i]; 7346 7347 if (idx <= children.length - 1) { 7348 node = children[idx]; 7349 } 7350 } 7351 7352 return node; 7353 }; 7354 7355 function setBookmarkEndPoint(start) { 7356 var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef; 7357 7358 if (endPoint) { 7359 moveLeft = endPoint.position > 0; 7360 7361 moveRng = body.createTextRange(); 7362 moveRng.moveToElementText(resolveIndexes(endPoint.indexes)); 7363 7364 offset = endPoint.offset; 7365 if (offset !== undef) { 7366 moveRng.collapse(endPoint.inside || moveLeft); 7367 moveRng.moveStart('character', moveLeft ? -offset : offset); 7368 } else 7369 moveRng.collapse(start); 7370 7371 rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng); 7372 7373 if (start) 7374 rng.collapse(true); 7375 } 7376 }; 7377 7378 if (bookmark.start) { 7379 if (bookmark.start.ctrl) { 7380 rng = body.createControlRange(); 7381 rng.addElement(resolveIndexes(bookmark.start.indexes)); 7382 rng.select(); 7383 } else { 7384 rng = body.createTextRange(); 7385 setBookmarkEndPoint(true); 7386 setBookmarkEndPoint(); 7387 rng.select(); 7388 } 7389 } 7390 }; 7391 7392 this.addRange = function(rng) { 7393 var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling, doc = selection.dom.doc, body = doc.body; 7394 7395 function setEndPoint(start) { 7396 var container, offset, marker, tmpRng, nodes; 7397 7398 marker = dom.create('a'); 7399 container = start ? startContainer : endContainer; 7400 offset = start ? startOffset : endOffset; 7401 tmpRng = ieRng.duplicate(); 7402 7403 if (container == doc || container == doc.documentElement) { 7404 container = body; 7405 offset = 0; 7406 } 7407 7408 if (container.nodeType == 3) { 7409 container.parentNode.insertBefore(marker, container); 7410 tmpRng.moveToElementText(marker); 7411 tmpRng.moveStart('character', offset); 7412 dom.remove(marker); 7413 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); 7414 } else { 7415 nodes = container.childNodes; 7416 7417 if (nodes.length) { 7418 if (offset >= nodes.length) { 7419 dom.insertAfter(marker, nodes[nodes.length - 1]); 7420 } else { 7421 container.insertBefore(marker, nodes[offset]); 7422 } 7423 7424 tmpRng.moveToElementText(marker); 7425 } else if (container.canHaveHTML) { 7426 // Empty node selection for example <div>|</div> 7427 // Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open 7428 container.innerHTML = '<span>\uFEFF</span>'; 7429 marker = container.firstChild; 7430 tmpRng.moveToElementText(marker); 7431 tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason 7432 } 7433 7434 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); 7435 dom.remove(marker); 7436 } 7437 } 7438 7439 // Setup some shorter versions 7440 startContainer = rng.startContainer; 7441 startOffset = rng.startOffset; 7442 endContainer = rng.endContainer; 7443 endOffset = rng.endOffset; 7444 ieRng = body.createTextRange(); 7445 7446 // If single element selection then try making a control selection out of it 7447 if (startContainer == endContainer && startContainer.nodeType == 1) { 7448 // Trick to place the caret inside an empty block element like <p></p> 7449 if (startOffset == endOffset && !startContainer.hasChildNodes()) { 7450 if (startContainer.canHaveHTML) { 7451 // Check if previous sibling is an empty block if it is then we need to render it 7452 // IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236 7453 // Example this: <p></p><p>|</p> would become this: <p>|</p><p></p> 7454 sibling = startContainer.previousSibling; 7455 if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) { 7456 sibling.innerHTML = '\uFEFF'; 7457 } else { 7458 sibling = null; 7459 } 7460 7461 startContainer.innerHTML = '<span>\uFEFF</span><span>\uFEFF</span>'; 7462 ieRng.moveToElementText(startContainer.lastChild); 7463 ieRng.select(); 7464 dom.doc.selection.clear(); 7465 startContainer.innerHTML = ''; 7466 7467 if (sibling) { 7468 sibling.innerHTML = ''; 7469 } 7470 return; 7471 } else { 7472 startOffset = dom.nodeIndex(startContainer); 7473 startContainer = startContainer.parentNode; 7474 } 7475 } 7476 7477 if (startOffset == endOffset - 1) { 7478 try { 7479 ctrlRng = body.createControlRange(); 7480 ctrlRng.addElement(startContainer.childNodes[startOffset]); 7481 ctrlRng.select(); 7482 return; 7483 } catch (ex) { 7484 // Ignore 7485 } 7486 } 7487 } 7488 7489 // Set start/end point of selection 7490 setEndPoint(true); 7491 setEndPoint(); 7492 7493 // Select the new range and scroll it into view 7494 ieRng.select(); 7495 }; 7496 7497 // Expose range method 7498 this.getRangeAt = getRange; 7499 }; 7500 7501 // Expose the selection object 7502 tinymce.dom.TridentSelection = Selection; 7503 })(); 7504 7505 7506 /* 7507 * Sizzle CSS Selector Engine 7508 * Copyright, The Dojo Foundation 7509 * Released under the MIT, BSD, and GPL Licenses. 7510 * More information: http://sizzlejs.com/ 7511 */ 7512 (function(){ 7513 7514 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, 7515 expando = "sizcache", 7516 done = 0, 7517 toString = Object.prototype.toString, 7518 hasDuplicate = false, 7519 baseHasDuplicate = true, 7520 rBackslash = /\\/g, 7521 rReturn = /\r\n/g, 7522 rNonWord = /\W/; 7523 7524 // Here we check if the JavaScript engine is using some sort of 7525 // optimization where it does not always call our comparision 7526 // function. If that is the case, discard the hasDuplicate value. 7527 // Thus far that includes Google Chrome. 7528 [0, 0].sort(function() { 7529 baseHasDuplicate = false; 7530 return 0; 7531 }); 7532 7533 var Sizzle = function( selector, context, results, seed ) { 7534 results = results || []; 7535 context = context || document; 7536 7537 var origContext = context; 7538 7539 if ( context.nodeType !== 1 && context.nodeType !== 9 ) { 7540 return []; 7541 } 7542 7543 if ( !selector || typeof selector !== "string" ) { 7544 return results; 7545 } 7546 7547 var m, set, checkSet, extra, ret, cur, pop, i, 7548 prune = true, 7549 contextXML = Sizzle.isXML( context ), 7550 parts = [], 7551 soFar = selector; 7552 7553 // Reset the position of the chunker regexp (start from head) 7554 do { 7555 chunker.exec( "" ); 7556 m = chunker.exec( soFar ); 7557 7558 if ( m ) { 7559 soFar = m[3]; 7560 7561 parts.push( m[1] ); 7562 7563 if ( m[2] ) { 7564 extra = m[3]; 7565 break; 7566 } 7567 } 7568 } while ( m ); 7569 7570 if ( parts.length > 1 && origPOS.exec( selector ) ) { 7571 7572 if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { 7573 set = posProcess( parts[0] + parts[1], context, seed ); 7574 7575 } else { 7576 set = Expr.relative[ parts[0] ] ? 7577 [ context ] : 7578 Sizzle( parts.shift(), context ); 7579 7580 while ( parts.length ) { 7581 selector = parts.shift(); 7582 7583 if ( Expr.relative[ selector ] ) { 7584 selector += parts.shift(); 7585 } 7586 7587 set = posProcess( selector, set, seed ); 7588 } 7589 } 7590 7591 } else { 7592 // Take a shortcut and set the context if the root selector is an ID 7593 // (but not if it'll be faster if the inner selector is an ID) 7594 if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && 7595 Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { 7596 7597 ret = Sizzle.find( parts.shift(), context, contextXML ); 7598 context = ret.expr ? 7599 Sizzle.filter( ret.expr, ret.set )[0] : 7600 ret.set[0]; 7601 } 7602 7603 if ( context ) { 7604 ret = seed ? 7605 { expr: parts.pop(), set: makeArray(seed) } : 7606 Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); 7607 7608 set = ret.expr ? 7609 Sizzle.filter( ret.expr, ret.set ) : 7610 ret.set; 7611 7612 if ( parts.length > 0 ) { 7613 checkSet = makeArray( set ); 7614 7615 } else { 7616 prune = false; 7617 } 7618 7619 while ( parts.length ) { 7620 cur = parts.pop(); 7621 pop = cur; 7622 7623 if ( !Expr.relative[ cur ] ) { 7624 cur = ""; 7625 } else { 7626 pop = parts.pop(); 7627 } 7628 7629 if ( pop == null ) { 7630 pop = context; 7631 } 7632 7633 Expr.relative[ cur ]( checkSet, pop, contextXML ); 7634 } 7635 7636 } else { 7637 checkSet = parts = []; 7638 } 7639 } 7640 7641 if ( !checkSet ) { 7642 checkSet = set; 7643 } 7644 7645 if ( !checkSet ) { 7646 Sizzle.error( cur || selector ); 7647 } 7648 7649 if ( toString.call(checkSet) === "[object Array]" ) { 7650 if ( !prune ) { 7651 results.push.apply( results, checkSet ); 7652 7653 } else if ( context && context.nodeType === 1 ) { 7654 for ( i = 0; checkSet[i] != null; i++ ) { 7655 if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { 7656 results.push( set[i] ); 7657 } 7658 } 7659 7660 } else { 7661 for ( i = 0; checkSet[i] != null; i++ ) { 7662 if ( checkSet[i] && checkSet[i].nodeType === 1 ) { 7663 results.push( set[i] ); 7664 } 7665 } 7666 } 7667 7668 } else { 7669 makeArray( checkSet, results ); 7670 } 7671 7672 if ( extra ) { 7673 Sizzle( extra, origContext, results, seed ); 7674 Sizzle.uniqueSort( results ); 7675 } 7676 7677 return results; 7678 }; 7679 7680 Sizzle.uniqueSort = function( results ) { 7681 if ( sortOrder ) { 7682 hasDuplicate = baseHasDuplicate; 7683 results.sort( sortOrder ); 7684 7685 if ( hasDuplicate ) { 7686 for ( var i = 1; i < results.length; i++ ) { 7687 if ( results[i] === results[ i - 1 ] ) { 7688 results.splice( i--, 1 ); 7689 } 7690 } 7691 } 7692 } 7693 7694 return results; 7695 }; 7696 7697 Sizzle.matches = function( expr, set ) { 7698 return Sizzle( expr, null, null, set ); 7699 }; 7700 7701 Sizzle.matchesSelector = function( node, expr ) { 7702 return Sizzle( expr, null, null, [node] ).length > 0; 7703 }; 7704 7705 Sizzle.find = function( expr, context, isXML ) { 7706 var set, i, len, match, type, left; 7707 7708 if ( !expr ) { 7709 return []; 7710 } 7711 7712 for ( i = 0, len = Expr.order.length; i < len; i++ ) { 7713 type = Expr.order[i]; 7714 7715 if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { 7716 left = match[1]; 7717 match.splice( 1, 1 ); 7718 7719 if ( left.substr( left.length - 1 ) !== "\\" ) { 7720 match[1] = (match[1] || "").replace( rBackslash, "" ); 7721 set = Expr.find[ type ]( match, context, isXML ); 7722 7723 if ( set != null ) { 7724 expr = expr.replace( Expr.match[ type ], "" ); 7725 break; 7726 } 7727 } 7728 } 7729 } 7730 7731 if ( !set ) { 7732 set = typeof context.getElementsByTagName !== "undefined" ? 7733 context.getElementsByTagName( "*" ) : 7734 []; 7735 } 7736 7737 return { set: set, expr: expr }; 7738 }; 7739 7740 Sizzle.filter = function( expr, set, inplace, not ) { 7741 var match, anyFound, 7742 type, found, item, filter, left, 7743 i, pass, 7744 old = expr, 7745 result = [], 7746 curLoop = set, 7747 isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); 7748 7749 while ( expr && set.length ) { 7750 for ( type in Expr.filter ) { 7751 if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { 7752 filter = Expr.filter[ type ]; 7753 left = match[1]; 7754 7755 anyFound = false; 7756 7757 match.splice(1,1); 7758 7759 if ( left.substr( left.length - 1 ) === "\\" ) { 7760 continue; 7761 } 7762 7763 if ( curLoop === result ) { 7764 result = []; 7765 } 7766 7767 if ( Expr.preFilter[ type ] ) { 7768 match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); 7769 7770 if ( !match ) { 7771 anyFound = found = true; 7772 7773 } else if ( match === true ) { 7774 continue; 7775 } 7776 } 7777 7778 if ( match ) { 7779 for ( i = 0; (item = curLoop[i]) != null; i++ ) { 7780 if ( item ) { 7781 found = filter( item, match, i, curLoop ); 7782 pass = not ^ found; 7783 7784 if ( inplace && found != null ) { 7785 if ( pass ) { 7786 anyFound = true; 7787 7788 } else { 7789 curLoop[i] = false; 7790 } 7791 7792 } else if ( pass ) { 7793 result.push( item ); 7794 anyFound = true; 7795 } 7796 } 7797 } 7798 } 7799 7800 if ( found !== undefined ) { 7801 if ( !inplace ) { 7802 curLoop = result; 7803 } 7804 7805 expr = expr.replace( Expr.match[ type ], "" ); 7806 7807 if ( !anyFound ) { 7808 return []; 7809 } 7810 7811 break; 7812 } 7813 } 7814 } 7815 7816 // Improper expression 7817 if ( expr === old ) { 7818 if ( anyFound == null ) { 7819 Sizzle.error( expr ); 7820 7821 } else { 7822 break; 7823 } 7824 } 7825 7826 old = expr; 7827 } 7828 7829 return curLoop; 7830 }; 7831 7832 Sizzle.error = function( msg ) { 7833 throw new Error( "Syntax error, unrecognized expression: " + msg ); 7834 }; 7835 7836 var getText = Sizzle.getText = function( elem ) { 7837 var i, node, 7838 nodeType = elem.nodeType, 7839 ret = ""; 7840 7841 if ( nodeType ) { 7842 if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { 7843 // Use textContent || innerText for elements 7844 if ( typeof elem.textContent === 'string' ) { 7845 return elem.textContent; 7846 } else if ( typeof elem.innerText === 'string' ) { 7847 // Replace IE's carriage returns 7848 return elem.innerText.replace( rReturn, '' ); 7849 } else { 7850 // Traverse it's children 7851 for ( elem = elem.firstChild; elem; elem = elem.nextSibling) { 7852 ret += getText( elem ); 7853 } 7854 } 7855 } else if ( nodeType === 3 || nodeType === 4 ) { 7856 return elem.nodeValue; 7857 } 7858 } else { 7859 7860 // If no nodeType, this is expected to be an array 7861 for ( i = 0; (node = elem[i]); i++ ) { 7862 // Do not traverse comment nodes 7863 if ( node.nodeType !== 8 ) { 7864 ret += getText( node ); 7865 } 7866 } 7867 } 7868 return ret; 7869 }; 7870 7871 var Expr = Sizzle.selectors = { 7872 order: [ "ID", "NAME", "TAG" ], 7873 7874 match: { 7875 ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, 7876 CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, 7877 NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, 7878 ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, 7879 TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, 7880 CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, 7881 POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, 7882 PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ 7883 }, 7884 7885 leftMatch: {}, 7886 7887 attrMap: { 7888 "class": "className", 7889 "for": "htmlFor" 7890 }, 7891 7892 attrHandle: { 7893 href: function( elem ) { 7894 return elem.getAttribute( "href" ); 7895 }, 7896 type: function( elem ) { 7897 return elem.getAttribute( "type" ); 7898 } 7899 }, 7900 7901 relative: { 7902 "+": function(checkSet, part){ 7903 var isPartStr = typeof part === "string", 7904 isTag = isPartStr && !rNonWord.test( part ), 7905 isPartStrNotTag = isPartStr && !isTag; 7906 7907 if ( isTag ) { 7908 part = part.toLowerCase(); 7909 } 7910 7911 for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { 7912 if ( (elem = checkSet[i]) ) { 7913 while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} 7914 7915 checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? 7916 elem || false : 7917 elem === part; 7918 } 7919 } 7920 7921 if ( isPartStrNotTag ) { 7922 Sizzle.filter( part, checkSet, true ); 7923 } 7924 }, 7925 7926 ">": function( checkSet, part ) { 7927 var elem, 7928 isPartStr = typeof part === "string", 7929 i = 0, 7930 l = checkSet.length; 7931 7932 if ( isPartStr && !rNonWord.test( part ) ) { 7933 part = part.toLowerCase(); 7934 7935 for ( ; i < l; i++ ) { 7936 elem = checkSet[i]; 7937 7938 if ( elem ) { 7939 var parent = elem.parentNode; 7940 checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; 7941 } 7942 } 7943 7944 } else { 7945 for ( ; i < l; i++ ) { 7946 elem = checkSet[i]; 7947 7948 if ( elem ) { 7949 checkSet[i] = isPartStr ? 7950 elem.parentNode : 7951 elem.parentNode === part; 7952 } 7953 } 7954 7955 if ( isPartStr ) { 7956 Sizzle.filter( part, checkSet, true ); 7957 } 7958 } 7959 }, 7960 7961 "": function(checkSet, part, isXML){ 7962 var nodeCheck, 7963 doneName = done++, 7964 checkFn = dirCheck; 7965 7966 if ( typeof part === "string" && !rNonWord.test( part ) ) { 7967 part = part.toLowerCase(); 7968 nodeCheck = part; 7969 checkFn = dirNodeCheck; 7970 } 7971 7972 checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); 7973 }, 7974 7975 "~": function( checkSet, part, isXML ) { 7976 var nodeCheck, 7977 doneName = done++, 7978 checkFn = dirCheck; 7979 7980 if ( typeof part === "string" && !rNonWord.test( part ) ) { 7981 part = part.toLowerCase(); 7982 nodeCheck = part; 7983 checkFn = dirNodeCheck; 7984 } 7985 7986 checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); 7987 } 7988 }, 7989 7990 find: { 7991 ID: function( match, context, isXML ) { 7992 if ( typeof context.getElementById !== "undefined" && !isXML ) { 7993 var m = context.getElementById(match[1]); 7994 // Check parentNode to catch when Blackberry 4.6 returns 7995 // nodes that are no longer in the document #6963 7996 return m && m.parentNode ? [m] : []; 7997 } 7998 }, 7999 8000 NAME: function( match, context ) { 8001 if ( typeof context.getElementsByName !== "undefined" ) { 8002 var ret = [], 8003 results = context.getElementsByName( match[1] ); 8004 8005 for ( var i = 0, l = results.length; i < l; i++ ) { 8006 if ( results[i].getAttribute("name") === match[1] ) { 8007 ret.push( results[i] ); 8008 } 8009 } 8010 8011 return ret.length === 0 ? null : ret; 8012 } 8013 }, 8014 8015 TAG: function( match, context ) { 8016 if ( typeof context.getElementsByTagName !== "undefined" ) { 8017 return context.getElementsByTagName( match[1] ); 8018 } 8019 } 8020 }, 8021 preFilter: { 8022 CLASS: function( match, curLoop, inplace, result, not, isXML ) { 8023 match = " " + match[1].replace( rBackslash, "" ) + " "; 8024 8025 if ( isXML ) { 8026 return match; 8027 } 8028 8029 for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { 8030 if ( elem ) { 8031 if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { 8032 if ( !inplace ) { 8033 result.push( elem ); 8034 } 8035 8036 } else if ( inplace ) { 8037 curLoop[i] = false; 8038 } 8039 } 8040 } 8041 8042 return false; 8043 }, 8044 8045 ID: function( match ) { 8046 return match[1].replace( rBackslash, "" ); 8047 }, 8048 8049 TAG: function( match, curLoop ) { 8050 return match[1].replace( rBackslash, "" ).toLowerCase(); 8051 }, 8052 8053 CHILD: function( match ) { 8054 if ( match[1] === "nth" ) { 8055 if ( !match[2] ) { 8056 Sizzle.error( match[0] ); 8057 } 8058 8059 match[2] = match[2].replace(/^\+|\s*/g, ''); 8060 8061 // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' 8062 var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( 8063 match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || 8064 !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); 8065 8066 // calculate the numbers (first)n+(last) including if they are negative 8067 match[2] = (test[1] + (test[2] || 1)) - 0; 8068 match[3] = test[3] - 0; 8069 } 8070 else if ( match[2] ) { 8071 Sizzle.error( match[0] ); 8072 } 8073 8074 // TODO: Move to normal caching system 8075 match[0] = done++; 8076 8077 return match; 8078 }, 8079 8080 ATTR: function( match, curLoop, inplace, result, not, isXML ) { 8081 var name = match[1] = match[1].replace( rBackslash, "" ); 8082 8083 if ( !isXML && Expr.attrMap[name] ) { 8084 match[1] = Expr.attrMap[name]; 8085 } 8086 8087 // Handle if an un-quoted value was used 8088 match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); 8089 8090 if ( match[2] === "~=" ) { 8091 match[4] = " " + match[4] + " "; 8092 } 8093 8094 return match; 8095 }, 8096 8097 PSEUDO: function( match, curLoop, inplace, result, not ) { 8098 if ( match[1] === "not" ) { 8099 // If we're dealing with a complex expression, or a simple one 8100 if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { 8101 match[3] = Sizzle(match[3], null, null, curLoop); 8102 8103 } else { 8104 var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); 8105 8106 if ( !inplace ) { 8107 result.push.apply( result, ret ); 8108 } 8109 8110 return false; 8111 } 8112 8113 } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { 8114 return true; 8115 } 8116 8117 return match; 8118 }, 8119 8120 POS: function( match ) { 8121 match.unshift( true ); 8122 8123 return match; 8124 } 8125 }, 8126 8127 filters: { 8128 enabled: function( elem ) { 8129 return elem.disabled === false && elem.type !== "hidden"; 8130 }, 8131 8132 disabled: function( elem ) { 8133 return elem.disabled === true; 8134 }, 8135 8136 checked: function( elem ) { 8137 return elem.checked === true; 8138 }, 8139 8140 selected: function( elem ) { 8141 // Accessing this property makes selected-by-default 8142 // options in Safari work properly 8143 if ( elem.parentNode ) { 8144 elem.parentNode.selectedIndex; 8145 } 8146 8147 return elem.selected === true; 8148 }, 8149 8150 parent: function( elem ) { 8151 return !!elem.firstChild; 8152 }, 8153 8154 empty: function( elem ) { 8155 return !elem.firstChild; 8156 }, 8157 8158 has: function( elem, i, match ) { 8159 return !!Sizzle( match[3], elem ).length; 8160 }, 8161 8162 header: function( elem ) { 8163 return (/h\d/i).test( elem.nodeName ); 8164 }, 8165 8166 text: function( elem ) { 8167 var attr = elem.getAttribute( "type" ), type = elem.type; 8168 // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) 8169 // use getAttribute instead to test this case 8170 return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null ); 8171 }, 8172 8173 radio: function( elem ) { 8174 return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; 8175 }, 8176 8177 checkbox: function( elem ) { 8178 return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; 8179 }, 8180 8181 file: function( elem ) { 8182 return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; 8183 }, 8184 8185 password: function( elem ) { 8186 return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; 8187 }, 8188 8189 submit: function( elem ) { 8190 var name = elem.nodeName.toLowerCase(); 8191 return (name === "input" || name === "button") && "submit" === elem.type; 8192 }, 8193 8194 image: function( elem ) { 8195 return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; 8196 }, 8197 8198 reset: function( elem ) { 8199 var name = elem.nodeName.toLowerCase(); 8200 return (name === "input" || name === "button") && "reset" === elem.type; 8201 }, 8202 8203 button: function( elem ) { 8204 var name = elem.nodeName.toLowerCase(); 8205 return name === "input" && "button" === elem.type || name === "button"; 8206 }, 8207 8208 input: function( elem ) { 8209 return (/input|select|textarea|button/i).test( elem.nodeName ); 8210 }, 8211 8212 focus: function( elem ) { 8213 return elem === elem.ownerDocument.activeElement; 8214 } 8215 }, 8216 setFilters: { 8217 first: function( elem, i ) { 8218 return i === 0; 8219 }, 8220 8221 last: function( elem, i, match, array ) { 8222 return i === array.length - 1; 8223 }, 8224 8225 even: function( elem, i ) { 8226 return i % 2 === 0; 8227 }, 8228 8229 odd: function( elem, i ) { 8230 return i % 2 === 1; 8231 }, 8232 8233 lt: function( elem, i, match ) { 8234 return i < match[3] - 0; 8235 }, 8236 8237 gt: function( elem, i, match ) { 8238 return i > match[3] - 0; 8239 }, 8240 8241 nth: function( elem, i, match ) { 8242 return match[3] - 0 === i; 8243 }, 8244 8245 eq: function( elem, i, match ) { 8246 return match[3] - 0 === i; 8247 } 8248 }, 8249 filter: { 8250 PSEUDO: function( elem, match, i, array ) { 8251 var name = match[1], 8252 filter = Expr.filters[ name ]; 8253 8254 if ( filter ) { 8255 return filter( elem, i, match, array ); 8256 8257 } else if ( name === "contains" ) { 8258 return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0; 8259 8260 } else if ( name === "not" ) { 8261 var not = match[3]; 8262 8263 for ( var j = 0, l = not.length; j < l; j++ ) { 8264 if ( not[j] === elem ) { 8265 return false; 8266 } 8267 } 8268 8269 return true; 8270 8271 } else { 8272 Sizzle.error( name ); 8273 } 8274 }, 8275 8276 CHILD: function( elem, match ) { 8277 var first, last, 8278 doneName, parent, cache, 8279 count, diff, 8280 type = match[1], 8281 node = elem; 8282 8283 switch ( type ) { 8284 case "only": 8285 case "first": 8286 while ( (node = node.previousSibling) ) { 8287 if ( node.nodeType === 1 ) { 8288 return false; 8289 } 8290 } 8291 8292 if ( type === "first" ) { 8293 return true; 8294 } 8295 8296 node = elem; 8297 8298 /* falls through */ 8299 case "last": 8300 while ( (node = node.nextSibling) ) { 8301 if ( node.nodeType === 1 ) { 8302 return false; 8303 } 8304 } 8305 8306 return true; 8307 8308 case "nth": 8309 first = match[2]; 8310 last = match[3]; 8311 8312 if ( first === 1 && last === 0 ) { 8313 return true; 8314 } 8315 8316 doneName = match[0]; 8317 parent = elem.parentNode; 8318 8319 if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) { 8320 count = 0; 8321 8322 for ( node = parent.firstChild; node; node = node.nextSibling ) { 8323 if ( node.nodeType === 1 ) { 8324 node.nodeIndex = ++count; 8325 } 8326 } 8327 8328 parent[ expando ] = doneName; 8329 } 8330 8331 diff = elem.nodeIndex - last; 8332 8333 if ( first === 0 ) { 8334 return diff === 0; 8335 8336 } else { 8337 return ( diff % first === 0 && diff / first >= 0 ); 8338 } 8339 } 8340 }, 8341 8342 ID: function( elem, match ) { 8343 return elem.nodeType === 1 && elem.getAttribute("id") === match; 8344 }, 8345 8346 TAG: function( elem, match ) { 8347 return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match; 8348 }, 8349 8350 CLASS: function( elem, match ) { 8351 return (" " + (elem.className || elem.getAttribute("class")) + " ") 8352 .indexOf( match ) > -1; 8353 }, 8354 8355 ATTR: function( elem, match ) { 8356 var name = match[1], 8357 result = Sizzle.attr ? 8358 Sizzle.attr( elem, name ) : 8359 Expr.attrHandle[ name ] ? 8360 Expr.attrHandle[ name ]( elem ) : 8361 elem[ name ] != null ? 8362 elem[ name ] : 8363 elem.getAttribute( name ), 8364 value = result + "", 8365 type = match[2], 8366 check = match[4]; 8367 8368 return result == null ? 8369 type === "!=" : 8370 !type && Sizzle.attr ? 8371 result != null : 8372 type === "=" ? 8373 value === check : 8374 type === "*=" ? 8375 value.indexOf(check) >= 0 : 8376 type === "~=" ? 8377 (" " + value + " ").indexOf(check) >= 0 : 8378 !check ? 8379 value && result !== false : 8380 type === "!=" ? 8381 value !== check : 8382 type === "^=" ? 8383 value.indexOf(check) === 0 : 8384 type === "$=" ? 8385 value.substr(value.length - check.length) === check : 8386 type === "|=" ? 8387 value === check || value.substr(0, check.length + 1) === check + "-" : 8388 false; 8389 }, 8390 8391 POS: function( elem, match, i, array ) { 8392 var name = match[2], 8393 filter = Expr.setFilters[ name ]; 8394 8395 if ( filter ) { 8396 return filter( elem, i, match, array ); 8397 } 8398 } 8399 } 8400 }; 8401 8402 var origPOS = Expr.match.POS, 8403 fescape = function(all, num){ 8404 return "\\" + (num - 0 + 1); 8405 }; 8406 8407 for ( var type in Expr.match ) { 8408 Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); 8409 Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); 8410 } 8411 // Expose origPOS 8412 // "global" as in regardless of relation to brackets/parens 8413 Expr.match.globalPOS = origPOS; 8414 8415 var makeArray = function( array, results ) { 8416 array = Array.prototype.slice.call( array, 0 ); 8417 8418 if ( results ) { 8419 results.push.apply( results, array ); 8420 return results; 8421 } 8422 8423 return array; 8424 }; 8425 8426 // Perform a simple check to determine if the browser is capable of 8427 // converting a NodeList to an array using builtin methods. 8428 // Also verifies that the returned array holds DOM nodes 8429 // (which is not the case in the Blackberry browser) 8430 try { 8431 Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; 8432 8433 // Provide a fallback method if it does not work 8434 } catch( e ) { 8435 makeArray = function( array, results ) { 8436 var i = 0, 8437 ret = results || []; 8438 8439 if ( toString.call(array) === "[object Array]" ) { 8440 Array.prototype.push.apply( ret, array ); 8441 8442 } else { 8443 if ( typeof array.length === "number" ) { 8444 for ( var l = array.length; i < l; i++ ) { 8445 ret.push( array[i] ); 8446 } 8447 8448 } else { 8449 for ( ; array[i]; i++ ) { 8450 ret.push( array[i] ); 8451 } 8452 } 8453 } 8454 8455 return ret; 8456 }; 8457 } 8458 8459 var sortOrder, siblingCheck; 8460 8461 if ( document.documentElement.compareDocumentPosition ) { 8462 sortOrder = function( a, b ) { 8463 if ( a === b ) { 8464 hasDuplicate = true; 8465 return 0; 8466 } 8467 8468 if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { 8469 return a.compareDocumentPosition ? -1 : 1; 8470 } 8471 8472 return a.compareDocumentPosition(b) & 4 ? -1 : 1; 8473 }; 8474 8475 } else { 8476 sortOrder = function( a, b ) { 8477 // The nodes are identical, we can exit early 8478 if ( a === b ) { 8479 hasDuplicate = true; 8480 return 0; 8481 8482 // Fallback to using sourceIndex (in IE) if it's available on both nodes 8483 } else if ( a.sourceIndex && b.sourceIndex ) { 8484 return a.sourceIndex - b.sourceIndex; 8485 } 8486 8487 var al, bl, 8488 ap = [], 8489 bp = [], 8490 aup = a.parentNode, 8491 bup = b.parentNode, 8492 cur = aup; 8493 8494 // If the nodes are siblings (or identical) we can do a quick check 8495 if ( aup === bup ) { 8496 return siblingCheck( a, b ); 8497 8498 // If no parents were found then the nodes are disconnected 8499 } else if ( !aup ) { 8500 return -1; 8501 8502 } else if ( !bup ) { 8503 return 1; 8504 } 8505 8506 // Otherwise they're somewhere else in the tree so we need 8507 // to build up a full list of the parentNodes for comparison 8508 while ( cur ) { 8509 ap.unshift( cur ); 8510 cur = cur.parentNode; 8511 } 8512 8513 cur = bup; 8514 8515 while ( cur ) { 8516 bp.unshift( cur ); 8517 cur = cur.parentNode; 8518 } 8519 8520 al = ap.length; 8521 bl = bp.length; 8522 8523 // Start walking down the tree looking for a discrepancy 8524 for ( var i = 0; i < al && i < bl; i++ ) { 8525 if ( ap[i] !== bp[i] ) { 8526 return siblingCheck( ap[i], bp[i] ); 8527 } 8528 } 8529 8530 // We ended someplace up the tree so do a sibling check 8531 return i === al ? 8532 siblingCheck( a, bp[i], -1 ) : 8533 siblingCheck( ap[i], b, 1 ); 8534 }; 8535 8536 siblingCheck = function( a, b, ret ) { 8537 if ( a === b ) { 8538 return ret; 8539 } 8540 8541 var cur = a.nextSibling; 8542 8543 while ( cur ) { 8544 if ( cur === b ) { 8545 return -1; 8546 } 8547 8548 cur = cur.nextSibling; 8549 } 8550 8551 return 1; 8552 }; 8553 } 8554 8555 // Check to see if the browser returns elements by name when 8556 // querying by getElementById (and provide a workaround) 8557 (function(){ 8558 // We're going to inject a fake input element with a specified name 8559 var form = document.createElement("div"), 8560 id = "script" + (new Date()).getTime(), 8561 root = document.documentElement; 8562 8563 form.innerHTML = "<a name='" + id + "'/>"; 8564 8565 // Inject it into the root element, check its status, and remove it quickly 8566 root.insertBefore( form, root.firstChild ); 8567 8568 // The workaround has to do additional checks after a getElementById 8569 // Which slows things down for other browsers (hence the branching) 8570 if ( document.getElementById( id ) ) { 8571 Expr.find.ID = function( match, context, isXML ) { 8572 if ( typeof context.getElementById !== "undefined" && !isXML ) { 8573 var m = context.getElementById(match[1]); 8574 8575 return m ? 8576 m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? 8577 [m] : 8578 undefined : 8579 []; 8580 } 8581 }; 8582 8583 Expr.filter.ID = function( elem, match ) { 8584 var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); 8585 8586 return elem.nodeType === 1 && node && node.nodeValue === match; 8587 }; 8588 } 8589 8590 root.removeChild( form ); 8591 8592 // release memory in IE 8593 root = form = null; 8594 })(); 8595 8596 (function(){ 8597 // Check to see if the browser returns only elements 8598 // when doing getElementsByTagName("*") 8599 8600 // Create a fake element 8601 var div = document.createElement("div"); 8602 div.appendChild( document.createComment("") ); 8603 8604 // Make sure no comments are found 8605 if ( div.getElementsByTagName("*").length > 0 ) { 8606 Expr.find.TAG = function( match, context ) { 8607 var results = context.getElementsByTagName( match[1] ); 8608 8609 // Filter out possible comments 8610 if ( match[1] === "*" ) { 8611 var tmp = []; 8612 8613 for ( var i = 0; results[i]; i++ ) { 8614 if ( results[i].nodeType === 1 ) { 8615 tmp.push( results[i] ); 8616 } 8617 } 8618 8619 results = tmp; 8620 } 8621 8622 return results; 8623 }; 8624 } 8625 8626 // Check to see if an attribute returns normalized href attributes 8627 div.innerHTML = "<a href='#'></a>"; 8628 8629 if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && 8630 div.firstChild.getAttribute("href") !== "#" ) { 8631 8632 Expr.attrHandle.href = function( elem ) { 8633 return elem.getAttribute( "href", 2 ); 8634 }; 8635 } 8636 8637 // release memory in IE 8638 div = null; 8639 })(); 8640 8641 if ( document.querySelectorAll ) { 8642 (function(){ 8643 var oldSizzle = Sizzle, 8644 div = document.createElement("div"), 8645 id = "__sizzle__"; 8646 8647 div.innerHTML = "<p class='TEST'></p>"; 8648 8649 // Safari can't handle uppercase or unicode characters when 8650 // in quirks mode. 8651 if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { 8652 return; 8653 } 8654 8655 Sizzle = function( query, context, extra, seed ) { 8656 context = context || document; 8657 8658 // Only use querySelectorAll on non-XML documents 8659 // (ID selectors don't work in non-HTML documents) 8660 if ( !seed && !Sizzle.isXML(context) ) { 8661 // See if we find a selector to speed up 8662 var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); 8663 8664 if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { 8665 // Speed-up: Sizzle("TAG") 8666 if ( match[1] ) { 8667 return makeArray( context.getElementsByTagName( query ), extra ); 8668 8669 // Speed-up: Sizzle(".CLASS") 8670 } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { 8671 return makeArray( context.getElementsByClassName( match[2] ), extra ); 8672 } 8673 } 8674 8675 if ( context.nodeType === 9 ) { 8676 // Speed-up: Sizzle("body") 8677 // The body element only exists once, optimize finding it 8678 if ( query === "body" && context.body ) { 8679 return makeArray( [ context.body ], extra ); 8680 8681 // Speed-up: Sizzle("#ID") 8682 } else if ( match && match[3] ) { 8683 var elem = context.getElementById( match[3] ); 8684 8685 // Check parentNode to catch when Blackberry 4.6 returns 8686 // nodes that are no longer in the document #6963 8687 if ( elem && elem.parentNode ) { 8688 // Handle the case where IE and Opera return items 8689 // by name instead of ID 8690 if ( elem.id === match[3] ) { 8691 return makeArray( [ elem ], extra ); 8692 } 8693 8694 } else { 8695 return makeArray( [], extra ); 8696 } 8697 } 8698 8699 try { 8700 return makeArray( context.querySelectorAll(query), extra ); 8701 } catch(qsaError) {} 8702 8703 // qSA works strangely on Element-rooted queries 8704 // We can work around this by specifying an extra ID on the root 8705 // and working up from there (Thanks to Andrew Dupont for the technique) 8706 // IE 8 doesn't work on object elements 8707 } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { 8708 var oldContext = context, 8709 old = context.getAttribute( "id" ), 8710 nid = old || id, 8711 hasParent = context.parentNode, 8712 relativeHierarchySelector = /^\s*[+~]/.test( query ); 8713 8714 if ( !old ) { 8715 context.setAttribute( "id", nid ); 8716 } else { 8717 nid = nid.replace( /'/g, "\\$&" ); 8718 } 8719 if ( relativeHierarchySelector && hasParent ) { 8720 context = context.parentNode; 8721 } 8722 8723 try { 8724 if ( !relativeHierarchySelector || hasParent ) { 8725 return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); 8726 } 8727 8728 } catch(pseudoError) { 8729 } finally { 8730 if ( !old ) { 8731 oldContext.removeAttribute( "id" ); 8732 } 8733 } 8734 } 8735 } 8736 8737 return oldSizzle(query, context, extra, seed); 8738 }; 8739 8740 for ( var prop in oldSizzle ) { 8741 Sizzle[ prop ] = oldSizzle[ prop ]; 8742 } 8743 8744 // release memory in IE 8745 div = null; 8746 })(); 8747 } 8748 8749 (function(){ 8750 var html = document.documentElement, 8751 matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; 8752 8753 if ( matches ) { 8754 // Check to see if it's possible to do matchesSelector 8755 // on a disconnected node (IE 9 fails this) 8756 var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ), 8757 pseudoWorks = false; 8758 8759 try { 8760 // This should fail with an exception 8761 // Gecko does not error, returns false instead 8762 matches.call( document.documentElement, "[test!='']:sizzle" ); 8763 8764 } catch( pseudoError ) { 8765 pseudoWorks = true; 8766 } 8767 8768 Sizzle.matchesSelector = function( node, expr ) { 8769 // Make sure that attribute selectors are quoted 8770 expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); 8771 8772 if ( !Sizzle.isXML( node ) ) { 8773 try { 8774 if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { 8775 var ret = matches.call( node, expr ); 8776 8777 // IE 9's matchesSelector returns false on disconnected nodes 8778 if ( ret || !disconnectedMatch || 8779 // As well, disconnected nodes are said to be in a document 8780 // fragment in IE 9, so check for that 8781 node.document && node.document.nodeType !== 11 ) { 8782 return ret; 8783 } 8784 } 8785 } catch(e) {} 8786 } 8787 8788 return Sizzle(expr, null, null, [node]).length > 0; 8789 }; 8790 } 8791 })(); 8792 8793 (function(){ 8794 var div = document.createElement("div"); 8795 8796 div.innerHTML = "<div class='test e'></div><div class='test'></div>"; 8797 8798 // Opera can't find a second classname (in 9.6) 8799 // Also, make sure that getElementsByClassName actually exists 8800 if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { 8801 return; 8802 } 8803 8804 // Safari caches class attributes, doesn't catch changes (in 3.2) 8805 div.lastChild.className = "e"; 8806 8807 if ( div.getElementsByClassName("e").length === 1 ) { 8808 return; 8809 } 8810 8811 Expr.order.splice(1, 0, "CLASS"); 8812 Expr.find.CLASS = function( match, context, isXML ) { 8813 if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { 8814 return context.getElementsByClassName(match[1]); 8815 } 8816 }; 8817 8818 // release memory in IE 8819 div = null; 8820 })(); 8821 8822 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { 8823 for ( var i = 0, l = checkSet.length; i < l; i++ ) { 8824 var elem = checkSet[i]; 8825 8826 if ( elem ) { 8827 var match = false; 8828 8829 elem = elem[dir]; 8830 8831 while ( elem ) { 8832 if ( elem[ expando ] === doneName ) { 8833 match = checkSet[elem.sizset]; 8834 break; 8835 } 8836 8837 if ( elem.nodeType === 1 && !isXML ){ 8838 elem[ expando ] = doneName; 8839 elem.sizset = i; 8840 } 8841 8842 if ( elem.nodeName.toLowerCase() === cur ) { 8843 match = elem; 8844 break; 8845 } 8846 8847 elem = elem[dir]; 8848 } 8849 8850 checkSet[i] = match; 8851 } 8852 } 8853 } 8854 8855 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { 8856 for ( var i = 0, l = checkSet.length; i < l; i++ ) { 8857 var elem = checkSet[i]; 8858 8859 if ( elem ) { 8860 var match = false; 8861 8862 elem = elem[dir]; 8863 8864 while ( elem ) { 8865 if ( elem[ expando ] === doneName ) { 8866 match = checkSet[elem.sizset]; 8867 break; 8868 } 8869 8870 if ( elem.nodeType === 1 ) { 8871 if ( !isXML ) { 8872 elem[ expando ] = doneName; 8873 elem.sizset = i; 8874 } 8875 8876 if ( typeof cur !== "string" ) { 8877 if ( elem === cur ) { 8878 match = true; 8879 break; 8880 } 8881 8882 } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { 8883 match = elem; 8884 break; 8885 } 8886 } 8887 8888 elem = elem[dir]; 8889 } 8890 8891 checkSet[i] = match; 8892 } 8893 } 8894 } 8895 8896 if ( document.documentElement.contains ) { 8897 Sizzle.contains = function( a, b ) { 8898 return a !== b && (a.contains ? a.contains(b) : true); 8899 }; 8900 8901 } else if ( document.documentElement.compareDocumentPosition ) { 8902 Sizzle.contains = function( a, b ) { 8903 return !!(a.compareDocumentPosition(b) & 16); 8904 }; 8905 8906 } else { 8907 Sizzle.contains = function() { 8908 return false; 8909 }; 8910 } 8911 8912 Sizzle.isXML = function( elem ) { 8913 // documentElement is verified for cases where it doesn't yet exist 8914 // (such as loading iframes in IE - #4833) 8915 var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; 8916 8917 return documentElement ? documentElement.nodeName !== "HTML" : false; 8918 }; 8919 8920 var posProcess = function( selector, context, seed ) { 8921 var match, 8922 tmpSet = [], 8923 later = "", 8924 root = context.nodeType ? [context] : context; 8925 8926 // Position selectors must be done after the filter 8927 // And so must :not(positional) so we move all PSEUDOs to the end 8928 while ( (match = Expr.match.PSEUDO.exec( selector )) ) { 8929 later += match[0]; 8930 selector = selector.replace( Expr.match.PSEUDO, "" ); 8931 } 8932 8933 selector = Expr.relative[selector] ? selector + "*" : selector; 8934 8935 for ( var i = 0, l = root.length; i < l; i++ ) { 8936 Sizzle( selector, root[i], tmpSet, seed ); 8937 } 8938 8939 return Sizzle.filter( later, tmpSet ); 8940 }; 8941 8942 // EXPOSE 8943 8944 window.tinymce.dom.Sizzle = Sizzle; 8945 8946 })(); 8947 8948 8949 (function(tinymce) { 8950 tinymce.dom.Element = function(id, settings) { 8951 var t = this, dom, el; 8952 8953 t.settings = settings = settings || {}; 8954 t.id = id; 8955 t.dom = dom = settings.dom || tinymce.DOM; 8956 8957 // Only IE leaks DOM references, this is a lot faster 8958 if (!tinymce.isIE) 8959 el = dom.get(t.id); 8960 8961 tinymce.each( 8962 ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + 8963 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + 8964 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + 8965 'isHidden,setHTML,get').split(/,/), function(k) { 8966 t[k] = function() { 8967 var a = [id], i; 8968 8969 for (i = 0; i < arguments.length; i++) 8970 a.push(arguments[i]); 8971 8972 a = dom[k].apply(dom, a); 8973 t.update(k); 8974 8975 return a; 8976 }; 8977 } 8978 ); 8979 8980 tinymce.extend(t, { 8981 on : function(n, f, s) { 8982 return tinymce.dom.Event.add(t.id, n, f, s); 8983 }, 8984 8985 getXY : function() { 8986 return { 8987 x : parseInt(t.getStyle('left')), 8988 y : parseInt(t.getStyle('top')) 8989 }; 8990 }, 8991 8992 getSize : function() { 8993 var n = dom.get(t.id); 8994 8995 return { 8996 w : parseInt(t.getStyle('width') || n.clientWidth), 8997 h : parseInt(t.getStyle('height') || n.clientHeight) 8998 }; 8999 }, 9000 9001 moveTo : function(x, y) { 9002 t.setStyles({left : x, top : y}); 9003 }, 9004 9005 moveBy : function(x, y) { 9006 var p = t.getXY(); 9007 9008 t.moveTo(p.x + x, p.y + y); 9009 }, 9010 9011 resizeTo : function(w, h) { 9012 t.setStyles({width : w, height : h}); 9013 }, 9014 9015 resizeBy : function(w, h) { 9016 var s = t.getSize(); 9017 9018 t.resizeTo(s.w + w, s.h + h); 9019 }, 9020 9021 update : function(k) { 9022 var b; 9023 9024 if (tinymce.isIE6 && settings.blocker) { 9025 k = k || ''; 9026 9027 // Ignore getters 9028 if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0) 9029 return; 9030 9031 // Remove blocker on remove 9032 if (k == 'remove') { 9033 dom.remove(t.blocker); 9034 return; 9035 } 9036 9037 if (!t.blocker) { 9038 t.blocker = dom.uniqueId(); 9039 b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'}); 9040 dom.setStyle(b, 'opacity', 0); 9041 } else 9042 b = dom.get(t.blocker); 9043 9044 dom.setStyles(b, { 9045 left : t.getStyle('left', 1), 9046 top : t.getStyle('top', 1), 9047 width : t.getStyle('width', 1), 9048 height : t.getStyle('height', 1), 9049 display : t.getStyle('display', 1), 9050 zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1 9051 }); 9052 } 9053 } 9054 }); 9055 }; 9056 })(tinymce); 9057 9058 (function(tinymce) { 9059 function trimNl(s) { 9060 return s.replace(/[\n\r]+/g, ''); 9061 }; 9062 9063 // Shorten names 9064 var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each, TreeWalker = tinymce.dom.TreeWalker; 9065 9066 tinymce.create('tinymce.dom.Selection', { 9067 Selection : function(dom, win, serializer, editor) { 9068 var t = this; 9069 9070 t.dom = dom; 9071 t.win = win; 9072 t.serializer = serializer; 9073 t.editor = editor; 9074 9075 // Add events 9076 each([ 9077 'onBeforeSetContent', 9078 9079 'onBeforeGetContent', 9080 9081 'onSetContent', 9082 9083 'onGetContent' 9084 ], function(e) { 9085 t[e] = new tinymce.util.Dispatcher(t); 9086 }); 9087 9088 // No W3C Range support 9089 if (!t.win.getSelection) 9090 t.tridentSel = new tinymce.dom.TridentSelection(t); 9091 9092 if (tinymce.isIE && dom.boxModel) 9093 this._fixIESelection(); 9094 9095 // Prevent leaks 9096 tinymce.addUnload(t.destroy, t); 9097 }, 9098 9099 setCursorLocation: function(node, offset) { 9100 var t = this; var r = t.dom.createRng(); 9101 r.setStart(node, offset); 9102 r.setEnd(node, offset); 9103 t.setRng(r); 9104 t.collapse(false); 9105 }, 9106 getContent : function(s) { 9107 var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n; 9108 9109 s = s || {}; 9110 wb = wa = ''; 9111 s.get = true; 9112 s.format = s.format || 'html'; 9113 s.forced_root_block = ''; 9114 t.onBeforeGetContent.dispatch(t, s); 9115 9116 if (s.format == 'text') 9117 return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : '')); 9118 9119 if (r.cloneContents) { 9120 n = r.cloneContents(); 9121 9122 if (n) 9123 e.appendChild(n); 9124 } else if (is(r.item) || is(r.htmlText)) { 9125 // IE will produce invalid markup if elements are present that 9126 // it doesn't understand like custom elements or HTML5 elements. 9127 // Adding a BR in front of the contents and then remoiving it seems to fix it though. 9128 e.innerHTML = '<br>' + (r.item ? r.item(0).outerHTML : r.htmlText); 9129 e.removeChild(e.firstChild); 9130 } else 9131 e.innerHTML = r.toString(); 9132 9133 // Keep whitespace before and after 9134 if (/^\s/.test(e.innerHTML)) 9135 wb = ' '; 9136 9137 if (/\s+$/.test(e.innerHTML)) 9138 wa = ' '; 9139 9140 s.getInner = true; 9141 9142 s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa; 9143 t.onGetContent.dispatch(t, s); 9144 9145 return s.content; 9146 }, 9147 9148 setContent : function(content, args) { 9149 var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp; 9150 9151 args = args || {format : 'html'}; 9152 args.set = true; 9153 content = args.content = content; 9154 9155 // Dispatch before set content event 9156 if (!args.no_events) 9157 self.onBeforeSetContent.dispatch(self, args); 9158 9159 content = args.content; 9160 9161 if (rng.insertNode) { 9162 // Make caret marker since insertNode places the caret in the beginning of text after insert 9163 content += '<span id="__caret">_</span>'; 9164 9165 // Delete and insert new node 9166 if (rng.startContainer == doc && rng.endContainer == doc) { 9167 // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents 9168 doc.body.innerHTML = content; 9169 } else { 9170 rng.deleteContents(); 9171 9172 if (doc.body.childNodes.length === 0) { 9173 doc.body.innerHTML = content; 9174 } else { 9175 // createContextualFragment doesn't exists in IE 9 DOMRanges 9176 if (rng.createContextualFragment) { 9177 rng.insertNode(rng.createContextualFragment(content)); 9178 } else { 9179 // Fake createContextualFragment call in IE 9 9180 frag = doc.createDocumentFragment(); 9181 temp = doc.createElement('div'); 9182 9183 frag.appendChild(temp); 9184 temp.outerHTML = content; 9185 9186 rng.insertNode(frag); 9187 } 9188 } 9189 } 9190 9191 // Move to caret marker 9192 caretNode = self.dom.get('__caret'); 9193 9194 // Make sure we wrap it compleatly, Opera fails with a simple select call 9195 rng = doc.createRange(); 9196 rng.setStartBefore(caretNode); 9197 rng.setEndBefore(caretNode); 9198 self.setRng(rng); 9199 9200 // Remove the caret position 9201 self.dom.remove('__caret'); 9202 9203 try { 9204 self.setRng(rng); 9205 } catch (ex) { 9206 // Might fail on Opera for some odd reason 9207 } 9208 } else { 9209 if (rng.item) { 9210 // Delete content and get caret text selection 9211 doc.execCommand('Delete', false, null); 9212 rng = self.getRng(); 9213 } 9214 9215 // Explorer removes spaces from the beginning of pasted contents 9216 if (/^\s+/.test(content)) { 9217 rng.pasteHTML('<span id="__mce_tmp">_</span>' + content); 9218 self.dom.remove('__mce_tmp'); 9219 } else 9220 rng.pasteHTML(content); 9221 } 9222 9223 // Dispatch set content event 9224 if (!args.no_events) 9225 self.onSetContent.dispatch(self, args); 9226 }, 9227 9228 getStart : function() { 9229 var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node; 9230 9231 if (rng.duplicate || rng.item) { 9232 // Control selection, return first item 9233 if (rng.item) 9234 return rng.item(0); 9235 9236 // Get start element 9237 checkRng = rng.duplicate(); 9238 checkRng.collapse(1); 9239 startElement = checkRng.parentElement(); 9240 if (startElement.ownerDocument !== self.dom.doc) { 9241 startElement = self.dom.getRoot(); 9242 } 9243 9244 // Check if range parent is inside the start element, then return the inner parent element 9245 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element 9246 parentElement = node = rng.parentElement(); 9247 while (node = node.parentNode) { 9248 if (node == startElement) { 9249 startElement = parentElement; 9250 break; 9251 } 9252 } 9253 9254 return startElement; 9255 } else { 9256 startElement = rng.startContainer; 9257 9258 if (startElement.nodeType == 1 && startElement.hasChildNodes()) 9259 startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)]; 9260 9261 if (startElement && startElement.nodeType == 3) 9262 return startElement.parentNode; 9263 9264 return startElement; 9265 } 9266 }, 9267 9268 getEnd : function() { 9269 var self = this, rng = self.getRng(), endElement, endOffset; 9270 9271 if (rng.duplicate || rng.item) { 9272 if (rng.item) 9273 return rng.item(0); 9274 9275 rng = rng.duplicate(); 9276 rng.collapse(0); 9277 endElement = rng.parentElement(); 9278 if (endElement.ownerDocument !== self.dom.doc) { 9279 endElement = self.dom.getRoot(); 9280 } 9281 9282 if (endElement && endElement.nodeName == 'BODY') 9283 return endElement.lastChild || endElement; 9284 9285 return endElement; 9286 } else { 9287 endElement = rng.endContainer; 9288 endOffset = rng.endOffset; 9289 9290 if (endElement.nodeType == 1 && endElement.hasChildNodes()) 9291 endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset]; 9292 9293 if (endElement && endElement.nodeType == 3) 9294 return endElement.parentNode; 9295 9296 return endElement; 9297 } 9298 }, 9299 9300 getBookmark : function(type, normalized) { 9301 var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles; 9302 9303 function findIndex(name, element) { 9304 var index = 0; 9305 9306 each(dom.select(name), function(node, i) { 9307 if (node == element) 9308 index = i; 9309 }); 9310 9311 return index; 9312 }; 9313 9314 function normalizeTableCellSelection(rng) { 9315 function moveEndPoint(start) { 9316 var container, offset, childNodes, prefix = start ? 'start' : 'end'; 9317 9318 container = rng[prefix + 'Container']; 9319 offset = rng[prefix + 'Offset']; 9320 9321 if (container.nodeType == 1 && container.nodeName == "TR") { 9322 childNodes = container.childNodes; 9323 container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)]; 9324 if (container) { 9325 offset = start ? 0 : container.childNodes.length; 9326 rng['set' + (start ? 'Start' : 'End')](container, offset); 9327 } 9328 } 9329 }; 9330 9331 moveEndPoint(true); 9332 moveEndPoint(); 9333 9334 return rng; 9335 }; 9336 9337 function getLocation() { 9338 var rng = t.getRng(true), root = dom.getRoot(), bookmark = {}; 9339 9340 function getPoint(rng, start) { 9341 var container = rng[start ? 'startContainer' : 'endContainer'], 9342 offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0; 9343 9344 if (container.nodeType == 3) { 9345 if (normalized) { 9346 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) 9347 offset += node.nodeValue.length; 9348 } 9349 9350 point.push(offset); 9351 } else { 9352 childNodes = container.childNodes; 9353 9354 if (offset >= childNodes.length && childNodes.length) { 9355 after = 1; 9356 offset = Math.max(0, childNodes.length - 1); 9357 } 9358 9359 point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after); 9360 } 9361 9362 for (; container && container != root; container = container.parentNode) 9363 point.push(t.dom.nodeIndex(container, normalized)); 9364 9365 return point; 9366 }; 9367 9368 bookmark.start = getPoint(rng, true); 9369 9370 if (!t.isCollapsed()) 9371 bookmark.end = getPoint(rng); 9372 9373 return bookmark; 9374 }; 9375 9376 if (type == 2) { 9377 if (t.tridentSel) 9378 return t.tridentSel.getBookmark(type); 9379 9380 return getLocation(); 9381 } 9382 9383 // Handle simple range 9384 if (type) 9385 return {rng : t.getRng()}; 9386 9387 rng = t.getRng(); 9388 id = dom.uniqueId(); 9389 collapsed = tinyMCE.activeEditor.selection.isCollapsed(); 9390 styles = 'overflow:hidden;line-height:0px'; 9391 9392 // Explorer method 9393 if (rng.duplicate || rng.item) { 9394 // Text selection 9395 if (!rng.item) { 9396 rng2 = rng.duplicate(); 9397 9398 try { 9399 // Insert start marker 9400 rng.collapse(); 9401 rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>'); 9402 9403 // Insert end marker 9404 if (!collapsed) { 9405 rng2.collapse(false); 9406 9407 // Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p> 9408 rng.moveToElementText(rng2.parentElement()); 9409 if (rng.compareEndPoints('StartToEnd', rng2) === 0) 9410 rng2.move('character', -1); 9411 9412 rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>'); 9413 } 9414 } catch (ex) { 9415 // IE might throw unspecified error so lets ignore it 9416 return null; 9417 } 9418 } else { 9419 // Control selection 9420 element = rng.item(0); 9421 name = element.nodeName; 9422 9423 return {name : name, index : findIndex(name, element)}; 9424 } 9425 } else { 9426 element = t.getNode(); 9427 name = element.nodeName; 9428 if (name == 'IMG') 9429 return {name : name, index : findIndex(name, element)}; 9430 9431 // W3C method 9432 rng2 = normalizeTableCellSelection(rng.cloneRange()); 9433 9434 // Insert end marker 9435 if (!collapsed) { 9436 rng2.collapse(false); 9437 rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr)); 9438 } 9439 9440 rng = normalizeTableCellSelection(rng); 9441 rng.collapse(true); 9442 rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr)); 9443 } 9444 9445 t.moveToBookmark({id : id, keep : 1}); 9446 9447 return {id : id}; 9448 }, 9449 9450 moveToBookmark : function(bookmark) { 9451 var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset; 9452 9453 function setEndPoint(start) { 9454 var point = bookmark[start ? 'start' : 'end'], i, node, offset, children; 9455 9456 if (point) { 9457 offset = point[0]; 9458 9459 // Find container node 9460 for (node = root, i = point.length - 1; i >= 1; i--) { 9461 children = node.childNodes; 9462 9463 if (point[i] > children.length - 1) 9464 return; 9465 9466 node = children[point[i]]; 9467 } 9468 9469 // Move text offset to best suitable location 9470 if (node.nodeType === 3) 9471 offset = Math.min(point[0], node.nodeValue.length); 9472 9473 // Move element offset to best suitable location 9474 if (node.nodeType === 1) 9475 offset = Math.min(point[0], node.childNodes.length); 9476 9477 // Set offset within container node 9478 if (start) 9479 rng.setStart(node, offset); 9480 else 9481 rng.setEnd(node, offset); 9482 } 9483 9484 return true; 9485 }; 9486 9487 function restoreEndPoint(suffix) { 9488 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep; 9489 9490 if (marker) { 9491 node = marker.parentNode; 9492 9493 if (suffix == 'start') { 9494 if (!keep) { 9495 idx = dom.nodeIndex(marker); 9496 } else { 9497 node = marker.firstChild; 9498 idx = 1; 9499 } 9500 9501 startContainer = endContainer = node; 9502 startOffset = endOffset = idx; 9503 } else { 9504 if (!keep) { 9505 idx = dom.nodeIndex(marker); 9506 } else { 9507 node = marker.firstChild; 9508 idx = 1; 9509 } 9510 9511 endContainer = node; 9512 endOffset = idx; 9513 } 9514 9515 if (!keep) { 9516 prev = marker.previousSibling; 9517 next = marker.nextSibling; 9518 9519 // Remove all marker text nodes 9520 each(tinymce.grep(marker.childNodes), function(node) { 9521 if (node.nodeType == 3) 9522 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, ''); 9523 }); 9524 9525 // Remove marker but keep children if for example contents where inserted into the marker 9526 // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature 9527 while (marker = dom.get(bookmark.id + '_' + suffix)) 9528 dom.remove(marker, 1); 9529 9530 // If siblings are text nodes then merge them unless it's Opera since it some how removes the node 9531 // and we are sniffing since adding a lot of detection code for a browser with 3% of the market isn't worth the effort. Sorry, Opera but it's just a fact 9532 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) { 9533 idx = prev.nodeValue.length; 9534 prev.appendData(next.nodeValue); 9535 dom.remove(next); 9536 9537 if (suffix == 'start') { 9538 startContainer = endContainer = prev; 9539 startOffset = endOffset = idx; 9540 } else { 9541 endContainer = prev; 9542 endOffset = idx; 9543 } 9544 } 9545 } 9546 } 9547 }; 9548 9549 function addBogus(node) { 9550 // Adds a bogus BR element for empty block elements 9551 if (dom.isBlock(node) && !node.innerHTML && !isIE) 9552 node.innerHTML = '<br data-mce-bogus="1" />'; 9553 9554 return node; 9555 }; 9556 9557 if (bookmark) { 9558 if (bookmark.start) { 9559 rng = dom.createRng(); 9560 root = dom.getRoot(); 9561 9562 if (t.tridentSel) 9563 return t.tridentSel.moveToBookmark(bookmark); 9564 9565 if (setEndPoint(true) && setEndPoint()) { 9566 t.setRng(rng); 9567 } 9568 } else if (bookmark.id) { 9569 // Restore start/end points 9570 restoreEndPoint('start'); 9571 restoreEndPoint('end'); 9572 9573 if (startContainer) { 9574 rng = dom.createRng(); 9575 rng.setStart(addBogus(startContainer), startOffset); 9576 rng.setEnd(addBogus(endContainer), endOffset); 9577 t.setRng(rng); 9578 } 9579 } else if (bookmark.name) { 9580 t.select(dom.select(bookmark.name)[bookmark.index]); 9581 } else if (bookmark.rng) 9582 t.setRng(bookmark.rng); 9583 } 9584 }, 9585 9586 select : function(node, content) { 9587 var t = this, dom = t.dom, rng = dom.createRng(), idx; 9588 9589 function setPoint(node, start) { 9590 var walker = new TreeWalker(node, node); 9591 9592 do { 9593 // Text node 9594 if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length !== 0) { 9595 if (start) 9596 rng.setStart(node, 0); 9597 else 9598 rng.setEnd(node, node.nodeValue.length); 9599 9600 return; 9601 } 9602 9603 // BR element 9604 if (node.nodeName == 'BR') { 9605 if (start) 9606 rng.setStartBefore(node); 9607 else 9608 rng.setEndBefore(node); 9609 9610 return; 9611 } 9612 } while (node = (start ? walker.next() : walker.prev())); 9613 }; 9614 9615 if (node) { 9616 idx = dom.nodeIndex(node); 9617 rng.setStart(node.parentNode, idx); 9618 rng.setEnd(node.parentNode, idx + 1); 9619 9620 // Find first/last text node or BR element 9621 if (content) { 9622 setPoint(node, 1); 9623 setPoint(node); 9624 } 9625 9626 t.setRng(rng); 9627 } 9628 9629 return node; 9630 }, 9631 9632 isCollapsed : function() { 9633 var t = this, r = t.getRng(), s = t.getSel(); 9634 9635 if (!r || r.item) 9636 return false; 9637 9638 if (r.compareEndPoints) 9639 return r.compareEndPoints('StartToEnd', r) === 0; 9640 9641 return !s || r.collapsed; 9642 }, 9643 9644 collapse : function(to_start) { 9645 var self = this, rng = self.getRng(), node; 9646 9647 // Control range on IE 9648 if (rng.item) { 9649 node = rng.item(0); 9650 rng = self.win.document.body.createTextRange(); 9651 rng.moveToElementText(node); 9652 } 9653 9654 rng.collapse(!!to_start); 9655 self.setRng(rng); 9656 }, 9657 9658 getSel : function() { 9659 var t = this, w = this.win; 9660 9661 return w.getSelection ? w.getSelection() : w.document.selection; 9662 }, 9663 9664 getRng : function(w3c) { 9665 var self = this, selection, rng, elm, doc = self.win.document; 9666 9667 // Found tridentSel object then we need to use that one 9668 if (w3c && self.tridentSel) { 9669 return self.tridentSel.getRangeAt(0); 9670 } 9671 9672 try { 9673 if (selection = self.getSel()) { 9674 rng = selection.rangeCount > 0 ? selection.getRangeAt(0) : (selection.createRange ? selection.createRange() : doc.createRange()); 9675 } 9676 } catch (ex) { 9677 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe 9678 } 9679 9680 // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet 9681 if (tinymce.isIE && rng && rng.setStart && doc.selection.createRange().item) { 9682 elm = doc.selection.createRange().item(0); 9683 rng = doc.createRange(); 9684 rng.setStartBefore(elm); 9685 rng.setEndAfter(elm); 9686 } 9687 9688 // No range found then create an empty one 9689 // This can occur when the editor is placed in a hidden container element on Gecko 9690 // Or on IE when there was an exception 9691 if (!rng) { 9692 rng = doc.createRange ? doc.createRange() : doc.body.createTextRange(); 9693 } 9694 9695 // If range is at start of document then move it to start of body 9696 if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) { 9697 elm = self.dom.getRoot(); 9698 rng.setStart(elm, 0); 9699 rng.setEnd(elm, 0); 9700 } 9701 9702 if (self.selectedRange && self.explicitRange) { 9703 if (rng.compareBoundaryPoints(rng.START_TO_START, self.selectedRange) === 0 && rng.compareBoundaryPoints(rng.END_TO_END, self.selectedRange) === 0) { 9704 // Safari, Opera and Chrome only ever select text which causes the range to change. 9705 // This lets us use the originally set range if the selection hasn't been changed by the user. 9706 rng = self.explicitRange; 9707 } else { 9708 self.selectedRange = null; 9709 self.explicitRange = null; 9710 } 9711 } 9712 9713 return rng; 9714 }, 9715 9716 setRng : function(r, forward) { 9717 var s, t = this; 9718 9719 if (!t.tridentSel) { 9720 s = t.getSel(); 9721 9722 if (s) { 9723 t.explicitRange = r; 9724 9725 try { 9726 s.removeAllRanges(); 9727 } catch (ex) { 9728 // IE9 might throw errors here don't know why 9729 } 9730 9731 s.addRange(r); 9732 9733 // Forward is set to false and we have an extend function 9734 if (forward === false && s.extend) { 9735 s.collapse(r.endContainer, r.endOffset); 9736 s.extend(r.startContainer, r.startOffset); 9737 } 9738 9739 // adding range isn't always successful so we need to check range count otherwise an exception can occur 9740 t.selectedRange = s.rangeCount > 0 ? s.getRangeAt(0) : null; 9741 } 9742 } else { 9743 // Is W3C Range 9744 if (r.cloneRange) { 9745 try { 9746 t.tridentSel.addRange(r); 9747 return; 9748 } catch (ex) { 9749 //IE9 throws an error here if called before selection is placed in the editor 9750 } 9751 } 9752 9753 // Is IE specific range 9754 try { 9755 r.select(); 9756 } catch (ex) { 9757 // Needed for some odd IE bug #1843306 9758 } 9759 } 9760 }, 9761 9762 setNode : function(n) { 9763 var t = this; 9764 9765 t.setContent(t.dom.getOuterHTML(n)); 9766 9767 return n; 9768 }, 9769 9770 getNode : function() { 9771 var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer; 9772 9773 function skipEmptyTextNodes(n, forwards) { 9774 var orig = n; 9775 while (n && n.nodeType === 3 && n.length === 0) { 9776 n = forwards ? n.nextSibling : n.previousSibling; 9777 } 9778 return n || orig; 9779 }; 9780 9781 // Range maybe lost after the editor is made visible again 9782 if (!rng) 9783 return t.dom.getRoot(); 9784 9785 if (rng.setStart) { 9786 elm = rng.commonAncestorContainer; 9787 9788 // Handle selection a image or other control like element such as anchors 9789 if (!rng.collapsed) { 9790 if (rng.startContainer == rng.endContainer) { 9791 if (rng.endOffset - rng.startOffset < 2) { 9792 if (rng.startContainer.hasChildNodes()) 9793 elm = rng.startContainer.childNodes[rng.startOffset]; 9794 } 9795 } 9796 9797 // If the anchor node is a element instead of a text node then return this element 9798 //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1) 9799 // return sel.anchorNode.childNodes[sel.anchorOffset]; 9800 9801 // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent. 9802 // This happens when you double click an underlined word in FireFox. 9803 if (start.nodeType === 3 && end.nodeType === 3) { 9804 if (start.length === rng.startOffset) { 9805 start = skipEmptyTextNodes(start.nextSibling, true); 9806 } else { 9807 start = start.parentNode; 9808 } 9809 if (rng.endOffset === 0) { 9810 end = skipEmptyTextNodes(end.previousSibling, false); 9811 } else { 9812 end = end.parentNode; 9813 } 9814 9815 if (start && start === end) 9816 return start; 9817 } 9818 } 9819 9820 if (elm && elm.nodeType == 3) 9821 return elm.parentNode; 9822 9823 return elm; 9824 } 9825 9826 return rng.item ? rng.item(0) : rng.parentElement(); 9827 }, 9828 9829 getSelectedBlocks : function(st, en) { 9830 var t = this, dom = t.dom, sb, eb, n, bl = []; 9831 9832 sb = dom.getParent(st || t.getStart(), dom.isBlock); 9833 eb = dom.getParent(en || t.getEnd(), dom.isBlock); 9834 9835 if (sb) 9836 bl.push(sb); 9837 9838 if (sb && eb && sb != eb) { 9839 n = sb; 9840 9841 var walker = new TreeWalker(sb, dom.getRoot()); 9842 while ((n = walker.next()) && n != eb) { 9843 if (dom.isBlock(n)) 9844 bl.push(n); 9845 } 9846 } 9847 9848 if (eb && sb != eb) 9849 bl.push(eb); 9850 9851 return bl; 9852 }, 9853 9854 isForward: function(){ 9855 var dom = this.dom, sel = this.getSel(), anchorRange, focusRange; 9856 9857 // No support for selection direction then always return true 9858 if (!sel || sel.anchorNode == null || sel.focusNode == null) { 9859 return true; 9860 } 9861 9862 anchorRange = dom.createRng(); 9863 anchorRange.setStart(sel.anchorNode, sel.anchorOffset); 9864 anchorRange.collapse(true); 9865 9866 focusRange = dom.createRng(); 9867 focusRange.setStart(sel.focusNode, sel.focusOffset); 9868 focusRange.collapse(true); 9869 9870 return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0; 9871 }, 9872 9873 normalize : function() { 9874 var self = this, rng, normalized, collapsed, node, sibling; 9875 9876 function normalizeEndPoint(start) { 9877 var container, offset, walker, dom = self.dom, body = dom.getRoot(), node, nonEmptyElementsMap, nodeName; 9878 9879 function hasBrBeforeAfter(node, left) { 9880 var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body); 9881 9882 while (node = walker[left ? 'prev' : 'next']()) { 9883 if (node.nodeName === "BR") { 9884 return true; 9885 } 9886 } 9887 }; 9888 9889 // Walks the dom left/right to find a suitable text node to move the endpoint into 9890 // It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG 9891 function findTextNodeRelative(left, startNode) { 9892 var walker, lastInlineElement; 9893 9894 startNode = startNode || container; 9895 walker = new TreeWalker(startNode, dom.getParent(startNode.parentNode, dom.isBlock) || body); 9896 9897 // Walk left until we hit a text node we can move to or a block/br/img 9898 while (node = walker[left ? 'prev' : 'next']()) { 9899 // Found text node that has a length 9900 if (node.nodeType === 3 && node.nodeValue.length > 0) { 9901 container = node; 9902 offset = left ? node.nodeValue.length : 0; 9903 normalized = true; 9904 return; 9905 } 9906 9907 // Break if we find a block or a BR/IMG/INPUT etc 9908 if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 9909 return; 9910 } 9911 9912 lastInlineElement = node; 9913 } 9914 9915 // Only fetch the last inline element when in caret mode for now 9916 if (collapsed && lastInlineElement) { 9917 container = lastInlineElement; 9918 normalized = true; 9919 offset = 0; 9920 } 9921 }; 9922 9923 container = rng[(start ? 'start' : 'end') + 'Container']; 9924 offset = rng[(start ? 'start' : 'end') + 'Offset']; 9925 nonEmptyElementsMap = dom.schema.getNonEmptyElements(); 9926 9927 // If the container is a document move it to the body element 9928 if (container.nodeType === 9) { 9929 container = dom.getRoot(); 9930 offset = 0; 9931 } 9932 9933 // If the container is body try move it into the closest text node or position 9934 if (container === body) { 9935 // If start is before/after a image, table etc 9936 if (start) { 9937 node = container.childNodes[offset > 0 ? offset - 1 : 0]; 9938 if (node) { 9939 nodeName = node.nodeName.toLowerCase(); 9940 if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") { 9941 return; 9942 } 9943 } 9944 } 9945 9946 // Resolve the index 9947 if (container.hasChildNodes()) { 9948 container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)]; 9949 offset = 0; 9950 9951 // Don't walk into elements that doesn't have any child nodes like a IMG 9952 if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) { 9953 // Walk the DOM to find a text node to place the caret at or a BR 9954 node = container; 9955 walker = new TreeWalker(container, body); 9956 9957 do { 9958 // Found a text node use that position 9959 if (node.nodeType === 3 && node.nodeValue.length > 0) { 9960 offset = start ? 0 : node.nodeValue.length; 9961 container = node; 9962 normalized = true; 9963 break; 9964 } 9965 9966 // Found a BR/IMG element that we can place the caret before 9967 if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 9968 offset = dom.nodeIndex(node); 9969 container = node.parentNode; 9970 9971 // Put caret after image when moving the end point 9972 if (node.nodeName == "IMG" && !start) { 9973 offset++; 9974 } 9975 9976 normalized = true; 9977 break; 9978 } 9979 } while (node = (start ? walker.next() : walker.prev())); 9980 } 9981 } 9982 } 9983 9984 // Lean the caret to the left if possible 9985 if (collapsed) { 9986 // So this: <b>x</b><i>|x</i> 9987 // Becomes: <b>x|</b><i>x</i> 9988 // Seems that only gecko has issues with this 9989 if (container.nodeType === 3 && offset === 0) { 9990 findTextNodeRelative(true); 9991 } 9992 9993 // Lean left into empty inline elements when the caret is before a BR 9994 // So this: <i><b></b><i>|<br></i> 9995 // Becomes: <i><b>|</b><i><br></i> 9996 // Seems that only gecko has issues with this 9997 if (container.nodeType === 1) { 9998 node = container.childNodes[offset]; 9999 if(node && node.nodeName === 'BR' && !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) { 10000 findTextNodeRelative(true, container.childNodes[offset]); 10001 } 10002 } 10003 } 10004 10005 // Lean the start of the selection right if possible 10006 // So this: x[<b>x]</b> 10007 // Becomes: x<b>[x]</b> 10008 if (start && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) { 10009 findTextNodeRelative(false); 10010 } 10011 10012 // Set endpoint if it was normalized 10013 if (normalized) 10014 rng['set' + (start ? 'Start' : 'End')](container, offset); 10015 }; 10016 10017 // Normalize only on non IE browsers for now 10018 if (tinymce.isIE) 10019 return; 10020 10021 rng = self.getRng(); 10022 collapsed = rng.collapsed; 10023 10024 // Normalize the end points 10025 normalizeEndPoint(true); 10026 10027 if (!collapsed) 10028 normalizeEndPoint(); 10029 10030 // Set the selection if it was normalized 10031 if (normalized) { 10032 // If it was collapsed then make sure it still is 10033 if (collapsed) { 10034 rng.collapse(true); 10035 } 10036 10037 //console.log(self.dom.dumpRng(rng)); 10038 self.setRng(rng, self.isForward()); 10039 } 10040 }, 10041 10042 selectorChanged: function(selector, callback) { 10043 var self = this, currentSelectors; 10044 10045 if (!self.selectorChangedData) { 10046 self.selectorChangedData = {}; 10047 currentSelectors = {}; 10048 10049 self.editor.onNodeChange.addToTop(function(ed, cm, node) { 10050 var dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {}; 10051 10052 // Check for new matching selectors 10053 each(self.selectorChangedData, function(callbacks, selector) { 10054 each(parents, function(node) { 10055 if (dom.is(node, selector)) { 10056 if (!currentSelectors[selector]) { 10057 // Execute callbacks 10058 each(callbacks, function(callback) { 10059 callback(true, {node: node, selector: selector, parents: parents}); 10060 }); 10061 10062 currentSelectors[selector] = callbacks; 10063 } 10064 10065 matchedSelectors[selector] = callbacks; 10066 return false; 10067 } 10068 }); 10069 }); 10070 10071 // Check if current selectors still match 10072 each(currentSelectors, function(callbacks, selector) { 10073 if (!matchedSelectors[selector]) { 10074 delete currentSelectors[selector]; 10075 10076 each(callbacks, function(callback) { 10077 callback(false, {node: node, selector: selector, parents: parents}); 10078 }); 10079 } 10080 }); 10081 }); 10082 } 10083 10084 // Add selector listeners 10085 if (!self.selectorChangedData[selector]) { 10086 self.selectorChangedData[selector] = []; 10087 } 10088 10089 self.selectorChangedData[selector].push(callback); 10090 10091 return self; 10092 }, 10093 10094 destroy : function(manual) { 10095 var self = this; 10096 10097 self.win = null; 10098 10099 // Manual destroy then remove unload handler 10100 if (!manual) 10101 tinymce.removeUnload(self.destroy); 10102 }, 10103 10104 // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode 10105 _fixIESelection : function() { 10106 var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm; 10107 10108 // Return range from point or null if it failed 10109 function rngFromPoint(x, y) { 10110 var rng = body.createTextRange(); 10111 10112 try { 10113 rng.moveToPoint(x, y); 10114 } catch (ex) { 10115 // IE sometimes throws and exception, so lets just ignore it 10116 rng = null; 10117 } 10118 10119 return rng; 10120 }; 10121 10122 // Fires while the selection is changing 10123 function selectionChange(e) { 10124 var pointRng; 10125 10126 // Check if the button is down or not 10127 if (e.button) { 10128 // Create range from mouse position 10129 pointRng = rngFromPoint(e.x, e.y); 10130 10131 if (pointRng) { 10132 // Check if pointRange is before/after selection then change the endPoint 10133 if (pointRng.compareEndPoints('StartToStart', startRng) > 0) 10134 pointRng.setEndPoint('StartToStart', startRng); 10135 else 10136 pointRng.setEndPoint('EndToEnd', startRng); 10137 10138 pointRng.select(); 10139 } 10140 } else 10141 endSelection(); 10142 } 10143 10144 // Removes listeners 10145 function endSelection() { 10146 var rng = doc.selection.createRange(); 10147 10148 // If the range is collapsed then use the last start range 10149 if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0) 10150 startRng.select(); 10151 10152 dom.unbind(doc, 'mouseup', endSelection); 10153 dom.unbind(doc, 'mousemove', selectionChange); 10154 startRng = started = 0; 10155 }; 10156 10157 // Make HTML element unselectable since we are going to handle selection by hand 10158 doc.documentElement.unselectable = true; 10159 10160 // Detect when user selects outside BODY 10161 dom.bind(doc, ['mousedown', 'contextmenu'], function(e) { 10162 if (e.target.nodeName === 'HTML') { 10163 if (started) 10164 endSelection(); 10165 10166 // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML 10167 htmlElm = doc.documentElement; 10168 if (htmlElm.scrollHeight > htmlElm.clientHeight) 10169 return; 10170 10171 started = 1; 10172 // Setup start position 10173 startRng = rngFromPoint(e.x, e.y); 10174 if (startRng) { 10175 // Listen for selection change events 10176 dom.bind(doc, 'mouseup', endSelection); 10177 dom.bind(doc, 'mousemove', selectionChange); 10178 10179 dom.win.focus(); 10180 startRng.select(); 10181 } 10182 } 10183 }); 10184 } 10185 }); 10186 })(tinymce); 10187 10188 (function(tinymce) { 10189 tinymce.dom.Serializer = function(settings, dom, schema) { 10190 var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser; 10191 10192 // Support the old apply_source_formatting option 10193 if (!settings.apply_source_formatting) 10194 settings.indent = false; 10195 10196 // Default DOM and Schema if they are undefined 10197 dom = dom || tinymce.DOM; 10198 schema = schema || new tinymce.html.Schema(settings); 10199 settings.entity_encoding = settings.entity_encoding || 'named'; 10200 settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true; 10201 10202 onPreProcess = new tinymce.util.Dispatcher(self); 10203 10204 onPostProcess = new tinymce.util.Dispatcher(self); 10205 10206 htmlParser = new tinymce.html.DomParser(settings, schema); 10207 10208 // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed 10209 htmlParser.addAttributeFilter('src,href,style', function(nodes, name) { 10210 var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef; 10211 10212 while (i--) { 10213 node = nodes[i]; 10214 10215 value = node.attributes.map[internalName]; 10216 if (value !== undef) { 10217 // Set external name to internal value and remove internal 10218 node.attr(name, value.length > 0 ? value : null); 10219 node.attr(internalName, null); 10220 } else { 10221 // No internal attribute found then convert the value we have in the DOM 10222 value = node.attributes.map[name]; 10223 10224 if (name === "style") 10225 value = dom.serializeStyle(dom.parseStyle(value), node.name); 10226 else if (urlConverter) 10227 value = urlConverter.call(urlConverterScope, value, name, node.name); 10228 10229 node.attr(name, value.length > 0 ? value : null); 10230 } 10231 } 10232 }); 10233 10234 // Remove internal classes mceItem<..> or mceSelected 10235 htmlParser.addAttributeFilter('class', function(nodes, name) { 10236 var i = nodes.length, node, value; 10237 10238 while (i--) { 10239 node = nodes[i]; 10240 value = node.attr('class').replace(/(?:^|\s)mce(Item\w+|Selected)(?!\S)/g, ''); 10241 node.attr('class', value.length > 0 ? value : null); 10242 } 10243 }); 10244 10245 // Remove bookmark elements 10246 htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) { 10247 var i = nodes.length, node; 10248 10249 while (i--) { 10250 node = nodes[i]; 10251 10252 if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup) 10253 node.remove(); 10254 } 10255 }); 10256 10257 // Remove expando attributes 10258 htmlParser.addAttributeFilter('data-mce-expando', function(nodes, name, args) { 10259 var i = nodes.length; 10260 10261 while (i--) { 10262 nodes[i].attr(name, null); 10263 } 10264 }); 10265 10266 // Force script into CDATA sections and remove the mce- prefix also add comments around styles 10267 htmlParser.addNodeFilter('script,style', function(nodes, name) { 10268 var i = nodes.length, node, value; 10269 10270 function trim(value) { 10271 return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n') 10272 .replace(/^[\r\n]*|[\r\n]*$/g, '') 10273 .replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '') 10274 .replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, ''); 10275 }; 10276 10277 while (i--) { 10278 node = nodes[i]; 10279 value = node.firstChild ? node.firstChild.value : ''; 10280 10281 if (name === "script") { 10282 // Remove mce- prefix from script elements 10283 node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, '')); 10284 10285 if (value.length > 0) 10286 node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>'; 10287 } else { 10288 if (value.length > 0) 10289 node.firstChild.value = '<!--\n' + trim(value) + '\n-->'; 10290 } 10291 } 10292 }); 10293 10294 // Convert comments to cdata and handle protected comments 10295 htmlParser.addNodeFilter('#comment', function(nodes, name) { 10296 var i = nodes.length, node; 10297 10298 while (i--) { 10299 node = nodes[i]; 10300 10301 if (node.value.indexOf('[CDATA[') === 0) { 10302 node.name = '#cdata'; 10303 node.type = 4; 10304 node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, ''); 10305 } else if (node.value.indexOf('mce:protected ') === 0) { 10306 node.name = "#text"; 10307 node.type = 3; 10308 node.raw = true; 10309 node.value = unescape(node.value).substr(14); 10310 } 10311 } 10312 }); 10313 10314 htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) { 10315 var i = nodes.length, node; 10316 10317 while (i--) { 10318 node = nodes[i]; 10319 if (node.type === 7) 10320 node.remove(); 10321 else if (node.type === 1) { 10322 if (name === "input" && !("type" in node.attributes.map)) 10323 node.attr('type', 'text'); 10324 } 10325 } 10326 }); 10327 10328 // Fix list elements, TODO: Replace this later 10329 if (settings.fix_list_elements) { 10330 htmlParser.addNodeFilter('ul,ol', function(nodes, name) { 10331 var i = nodes.length, node, parentNode; 10332 10333 while (i--) { 10334 node = nodes[i]; 10335 parentNode = node.parent; 10336 10337 if (parentNode.name === 'ul' || parentNode.name === 'ol') { 10338 if (node.prev && node.prev.name === 'li') { 10339 node.prev.append(node); 10340 } 10341 } 10342 } 10343 }); 10344 } 10345 10346 // Remove internal data attributes 10347 htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) { 10348 var i = nodes.length; 10349 10350 while (i--) { 10351 nodes[i].attr(name, null); 10352 } 10353 }); 10354 10355 // Return public methods 10356 return { 10357 schema : schema, 10358 10359 addNodeFilter : htmlParser.addNodeFilter, 10360 10361 addAttributeFilter : htmlParser.addAttributeFilter, 10362 10363 onPreProcess : onPreProcess, 10364 10365 onPostProcess : onPostProcess, 10366 10367 serialize : function(node, args) { 10368 var impl, doc, oldDoc, htmlSerializer, content; 10369 10370 // Explorer won't clone contents of script and style and the 10371 // selected index of select elements are cleared on a clone operation. 10372 if (isIE && dom.select('script,style,select,map').length > 0) { 10373 content = node.innerHTML; 10374 node = node.cloneNode(false); 10375 dom.setHTML(node, content); 10376 } else 10377 node = node.cloneNode(true); 10378 10379 // Nodes needs to be attached to something in WebKit/Opera 10380 // Older builds of Opera crashes if you attach the node to an document created dynamically 10381 // and since we can't feature detect a crash we need to sniff the acutal build number 10382 // This fix will make DOM ranges and make Sizzle happy! 10383 impl = node.ownerDocument.implementation; 10384 if (impl.createHTMLDocument) { 10385 // Create an empty HTML document 10386 doc = impl.createHTMLDocument(""); 10387 10388 // Add the element or it's children if it's a body element to the new document 10389 each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) { 10390 doc.body.appendChild(doc.importNode(node, true)); 10391 }); 10392 10393 // Grab first child or body element for serialization 10394 if (node.nodeName != 'BODY') 10395 node = doc.body.firstChild; 10396 else 10397 node = doc.body; 10398 10399 // set the new document in DOMUtils so createElement etc works 10400 oldDoc = dom.doc; 10401 dom.doc = doc; 10402 } 10403 10404 args = args || {}; 10405 args.format = args.format || 'html'; 10406 10407 // Pre process 10408 if (!args.no_events) { 10409 args.node = node; 10410 onPreProcess.dispatch(self, args); 10411 } 10412 10413 // Setup serializer 10414 htmlSerializer = new tinymce.html.Serializer(settings, schema); 10415 10416 // Parse and serialize HTML 10417 args.content = htmlSerializer.serialize( 10418 htmlParser.parse(tinymce.trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args) 10419 ); 10420 10421 // Replace all BOM characters for now until we can find a better solution 10422 if (!args.cleanup) 10423 args.content = args.content.replace(/\uFEFF|\u200B/g, ''); 10424 10425 // Post process 10426 if (!args.no_events) 10427 onPostProcess.dispatch(self, args); 10428 10429 // Restore the old document if it was changed 10430 if (oldDoc) 10431 dom.doc = oldDoc; 10432 10433 args.node = null; 10434 10435 return args.content; 10436 }, 10437 10438 addRules : function(rules) { 10439 schema.addValidElements(rules); 10440 }, 10441 10442 setRules : function(rules) { 10443 schema.setValidElements(rules); 10444 } 10445 }; 10446 }; 10447 })(tinymce); 10448 (function(tinymce) { 10449 tinymce.dom.ScriptLoader = function(settings) { 10450 var QUEUED = 0, 10451 LOADING = 1, 10452 LOADED = 2, 10453 states = {}, 10454 queue = [], 10455 scriptLoadedCallbacks = {}, 10456 queueLoadedCallbacks = [], 10457 loading = 0, 10458 undef; 10459 10460 function loadScript(url, callback) { 10461 var t = this, dom = tinymce.DOM, elm, uri, loc, id; 10462 10463 // Execute callback when script is loaded 10464 function done() { 10465 dom.remove(id); 10466 10467 if (elm) 10468 elm.onreadystatechange = elm.onload = elm = null; 10469 10470 callback(); 10471 }; 10472 10473 function error() { 10474 // Report the error so it's easier for people to spot loading errors 10475 if (typeof(console) !== "undefined" && console.log) 10476 console.log("Failed to load: " + url); 10477 10478 // We can't mark it as done if there is a load error since 10479 // A) We don't want to produce 404 errors on the server and 10480 // B) the onerror event won't fire on all browsers. 10481 // done(); 10482 }; 10483 10484 id = dom.uniqueId(); 10485 10486 if (tinymce.isIE6) { 10487 uri = new tinymce.util.URI(url); 10488 loc = location; 10489 10490 // If script is from same domain and we 10491 // use IE 6 then use XHR since it's more reliable 10492 if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') { 10493 tinymce.util.XHR.send({ 10494 url : tinymce._addVer(uri.getURI()), 10495 success : function(content) { 10496 // Create new temp script element 10497 var script = dom.create('script', { 10498 type : 'text/javascript' 10499 }); 10500 10501 // Evaluate script in global scope 10502 script.text = content; 10503 document.getElementsByTagName('head')[0].appendChild(script); 10504 dom.remove(script); 10505 10506 done(); 10507 }, 10508 10509 error : error 10510 }); 10511 10512 return; 10513 } 10514 } 10515 10516 // Create new script element 10517 elm = document.createElement('script'); 10518 elm.id = id; 10519 elm.type = 'text/javascript'; 10520 elm.src = tinymce._addVer(url); 10521 10522 // Add onload listener for non IE browsers since IE9 10523 // fires onload event before the script is parsed and executed 10524 if (!tinymce.isIE) 10525 elm.onload = done; 10526 10527 // Add onerror event will get fired on some browsers but not all of them 10528 elm.onerror = error; 10529 10530 // Opera 9.60 doesn't seem to fire the onreadystate event at correctly 10531 if (!tinymce.isOpera) { 10532 elm.onreadystatechange = function() { 10533 var state = elm.readyState; 10534 10535 // Loaded state is passed on IE 6 however there 10536 // are known issues with this method but we can't use 10537 // XHR in a cross domain loading 10538 if (state == 'complete' || state == 'loaded') 10539 done(); 10540 }; 10541 } 10542 10543 // Most browsers support this feature so we report errors 10544 // for those at least to help users track their missing plugins etc 10545 // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option 10546 /*elm.onerror = function() { 10547 alert('Failed to load: ' + url); 10548 };*/ 10549 10550 // Add script to document 10551 (document.getElementsByTagName('head')[0] || document.body).appendChild(elm); 10552 }; 10553 10554 this.isDone = function(url) { 10555 return states[url] == LOADED; 10556 }; 10557 10558 this.markDone = function(url) { 10559 states[url] = LOADED; 10560 }; 10561 10562 this.add = this.load = function(url, callback, scope) { 10563 var item, state = states[url]; 10564 10565 // Add url to load queue 10566 if (state == undef) { 10567 queue.push(url); 10568 states[url] = QUEUED; 10569 } 10570 10571 if (callback) { 10572 // Store away callback for later execution 10573 if (!scriptLoadedCallbacks[url]) 10574 scriptLoadedCallbacks[url] = []; 10575 10576 scriptLoadedCallbacks[url].push({ 10577 func : callback, 10578 scope : scope || this 10579 }); 10580 } 10581 }; 10582 10583 this.loadQueue = function(callback, scope) { 10584 this.loadScripts(queue, callback, scope); 10585 }; 10586 10587 this.loadScripts = function(scripts, callback, scope) { 10588 var loadScripts; 10589 10590 function execScriptLoadedCallbacks(url) { 10591 // Execute URL callback functions 10592 tinymce.each(scriptLoadedCallbacks[url], function(callback) { 10593 callback.func.call(callback.scope); 10594 }); 10595 10596 scriptLoadedCallbacks[url] = undef; 10597 }; 10598 10599 queueLoadedCallbacks.push({ 10600 func : callback, 10601 scope : scope || this 10602 }); 10603 10604 loadScripts = function() { 10605 var loadingScripts = tinymce.grep(scripts); 10606 10607 // Current scripts has been handled 10608 scripts.length = 0; 10609 10610 // Load scripts that needs to be loaded 10611 tinymce.each(loadingScripts, function(url) { 10612 // Script is already loaded then execute script callbacks directly 10613 if (states[url] == LOADED) { 10614 execScriptLoadedCallbacks(url); 10615 return; 10616 } 10617 10618 // Is script not loading then start loading it 10619 if (states[url] != LOADING) { 10620 states[url] = LOADING; 10621 loading++; 10622 10623 loadScript(url, function() { 10624 states[url] = LOADED; 10625 loading--; 10626 10627 execScriptLoadedCallbacks(url); 10628 10629 // Load more scripts if they where added by the recently loaded script 10630 loadScripts(); 10631 }); 10632 } 10633 }); 10634 10635 // No scripts are currently loading then execute all pending queue loaded callbacks 10636 if (!loading) { 10637 tinymce.each(queueLoadedCallbacks, function(callback) { 10638 callback.func.call(callback.scope); 10639 }); 10640 10641 queueLoadedCallbacks.length = 0; 10642 } 10643 }; 10644 10645 loadScripts(); 10646 }; 10647 }; 10648 10649 // Global script loader 10650 tinymce.ScriptLoader = new tinymce.dom.ScriptLoader(); 10651 })(tinymce); 10652 10653 (function(tinymce) { 10654 tinymce.dom.RangeUtils = function(dom) { 10655 var INVISIBLE_CHAR = '\uFEFF'; 10656 10657 this.walk = function(rng, callback) { 10658 var startContainer = rng.startContainer, 10659 startOffset = rng.startOffset, 10660 endContainer = rng.endContainer, 10661 endOffset = rng.endOffset, 10662 ancestor, startPoint, 10663 endPoint, node, parent, siblings, nodes; 10664 10665 // Handle table cell selection the table plugin enables 10666 // you to fake select table cells and perform formatting actions on them 10667 nodes = dom.select('td.mceSelected,th.mceSelected'); 10668 if (nodes.length > 0) { 10669 tinymce.each(nodes, function(node) { 10670 callback([node]); 10671 }); 10672 10673 return; 10674 } 10675 10676 function exclude(nodes) { 10677 var node; 10678 10679 // First node is excluded 10680 node = nodes[0]; 10681 if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) { 10682 nodes.splice(0, 1); 10683 } 10684 10685 // Last node is excluded 10686 node = nodes[nodes.length - 1]; 10687 if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) { 10688 nodes.splice(nodes.length - 1, 1); 10689 } 10690 10691 return nodes; 10692 }; 10693 10694 function collectSiblings(node, name, end_node) { 10695 var siblings = []; 10696 10697 for (; node && node != end_node; node = node[name]) 10698 siblings.push(node); 10699 10700 return siblings; 10701 }; 10702 10703 function findEndPoint(node, root) { 10704 do { 10705 if (node.parentNode == root) 10706 return node; 10707 10708 node = node.parentNode; 10709 } while(node); 10710 }; 10711 10712 function walkBoundary(start_node, end_node, next) { 10713 var siblingName = next ? 'nextSibling' : 'previousSibling'; 10714 10715 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) { 10716 parent = node.parentNode; 10717 siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName); 10718 10719 if (siblings.length) { 10720 if (!next) 10721 siblings.reverse(); 10722 10723 callback(exclude(siblings)); 10724 } 10725 } 10726 }; 10727 10728 // If index based start position then resolve it 10729 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) 10730 startContainer = startContainer.childNodes[startOffset]; 10731 10732 // If index based end position then resolve it 10733 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) 10734 endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)]; 10735 10736 // Same container 10737 if (startContainer == endContainer) 10738 return callback(exclude([startContainer])); 10739 10740 // Find common ancestor and end points 10741 ancestor = dom.findCommonAncestor(startContainer, endContainer); 10742 10743 // Process left side 10744 for (node = startContainer; node; node = node.parentNode) { 10745 if (node === endContainer) 10746 return walkBoundary(startContainer, ancestor, true); 10747 10748 if (node === ancestor) 10749 break; 10750 } 10751 10752 // Process right side 10753 for (node = endContainer; node; node = node.parentNode) { 10754 if (node === startContainer) 10755 return walkBoundary(endContainer, ancestor); 10756 10757 if (node === ancestor) 10758 break; 10759 } 10760 10761 // Find start/end point 10762 startPoint = findEndPoint(startContainer, ancestor) || startContainer; 10763 endPoint = findEndPoint(endContainer, ancestor) || endContainer; 10764 10765 // Walk left leaf 10766 walkBoundary(startContainer, startPoint, true); 10767 10768 // Walk the middle from start to end point 10769 siblings = collectSiblings( 10770 startPoint == startContainer ? startPoint : startPoint.nextSibling, 10771 'nextSibling', 10772 endPoint == endContainer ? endPoint.nextSibling : endPoint 10773 ); 10774 10775 if (siblings.length) 10776 callback(exclude(siblings)); 10777 10778 // Walk right leaf 10779 walkBoundary(endContainer, endPoint); 10780 }; 10781 10782 this.split = function(rng) { 10783 var startContainer = rng.startContainer, 10784 startOffset = rng.startOffset, 10785 endContainer = rng.endContainer, 10786 endOffset = rng.endOffset; 10787 10788 function splitText(node, offset) { 10789 return node.splitText(offset); 10790 }; 10791 10792 // Handle single text node 10793 if (startContainer == endContainer && startContainer.nodeType == 3) { 10794 if (startOffset > 0 && startOffset < startContainer.nodeValue.length) { 10795 endContainer = splitText(startContainer, startOffset); 10796 startContainer = endContainer.previousSibling; 10797 10798 if (endOffset > startOffset) { 10799 endOffset = endOffset - startOffset; 10800 startContainer = endContainer = splitText(endContainer, endOffset).previousSibling; 10801 endOffset = endContainer.nodeValue.length; 10802 startOffset = 0; 10803 } else { 10804 endOffset = 0; 10805 } 10806 } 10807 } else { 10808 // Split startContainer text node if needed 10809 if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) { 10810 startContainer = splitText(startContainer, startOffset); 10811 startOffset = 0; 10812 } 10813 10814 // Split endContainer text node if needed 10815 if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) { 10816 endContainer = splitText(endContainer, endOffset).previousSibling; 10817 endOffset = endContainer.nodeValue.length; 10818 } 10819 } 10820 10821 return { 10822 startContainer : startContainer, 10823 startOffset : startOffset, 10824 endContainer : endContainer, 10825 endOffset : endOffset 10826 }; 10827 }; 10828 10829 }; 10830 10831 tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) { 10832 if (rng1 && rng2) { 10833 // Compare native IE ranges 10834 if (rng1.item || rng1.duplicate) { 10835 // Both are control ranges and the selected element matches 10836 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) 10837 return true; 10838 10839 // Both are text ranges and the range matches 10840 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) 10841 return true; 10842 } else { 10843 // Compare w3c ranges 10844 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset; 10845 } 10846 } 10847 10848 return false; 10849 }; 10850 })(tinymce); 10851 10852 (function(tinymce) { 10853 var Event = tinymce.dom.Event, each = tinymce.each; 10854 10855 tinymce.create('tinymce.ui.KeyboardNavigation', { 10856 KeyboardNavigation: function(settings, dom) { 10857 var t = this, root = settings.root, items = settings.items, 10858 enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown, 10859 excludeFromTabOrder = settings.excludeFromTabOrder, 10860 itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId; 10861 10862 dom = dom || tinymce.DOM; 10863 10864 itemFocussed = function(evt) { 10865 focussedId = evt.target.id; 10866 }; 10867 10868 itemBlurred = function(evt) { 10869 dom.setAttrib(evt.target.id, 'tabindex', '-1'); 10870 }; 10871 10872 rootFocussed = function(evt) { 10873 var item = dom.get(focussedId); 10874 dom.setAttrib(item, 'tabindex', '0'); 10875 item.focus(); 10876 }; 10877 10878 t.focus = function() { 10879 dom.get(focussedId).focus(); 10880 }; 10881 10882 t.destroy = function() { 10883 each(items, function(item) { 10884 var elm = dom.get(item.id); 10885 10886 dom.unbind(elm, 'focus', itemFocussed); 10887 dom.unbind(elm, 'blur', itemBlurred); 10888 }); 10889 10890 var rootElm = dom.get(root); 10891 dom.unbind(rootElm, 'focus', rootFocussed); 10892 dom.unbind(rootElm, 'keydown', rootKeydown); 10893 10894 items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null; 10895 t.destroy = function() {}; 10896 }; 10897 10898 t.moveFocus = function(dir, evt) { 10899 var idx = -1, controls = t.controls, newFocus; 10900 10901 if (!focussedId) 10902 return; 10903 10904 each(items, function(item, index) { 10905 if (item.id === focussedId) { 10906 idx = index; 10907 return false; 10908 } 10909 }); 10910 10911 idx += dir; 10912 if (idx < 0) { 10913 idx = items.length - 1; 10914 } else if (idx >= items.length) { 10915 idx = 0; 10916 } 10917 10918 newFocus = items[idx]; 10919 dom.setAttrib(focussedId, 'tabindex', '-1'); 10920 dom.setAttrib(newFocus.id, 'tabindex', '0'); 10921 dom.get(newFocus.id).focus(); 10922 10923 if (settings.actOnFocus) { 10924 settings.onAction(newFocus.id); 10925 } 10926 10927 if (evt) 10928 Event.cancel(evt); 10929 }; 10930 10931 rootKeydown = function(evt) { 10932 var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32; 10933 10934 switch (evt.keyCode) { 10935 case DOM_VK_LEFT: 10936 if (enableLeftRight) t.moveFocus(-1); 10937 break; 10938 10939 case DOM_VK_RIGHT: 10940 if (enableLeftRight) t.moveFocus(1); 10941 break; 10942 10943 case DOM_VK_UP: 10944 if (enableUpDown) t.moveFocus(-1); 10945 break; 10946 10947 case DOM_VK_DOWN: 10948 if (enableUpDown) t.moveFocus(1); 10949 break; 10950 10951 case DOM_VK_ESCAPE: 10952 if (settings.onCancel) { 10953 settings.onCancel(); 10954 Event.cancel(evt); 10955 } 10956 break; 10957 10958 case DOM_VK_ENTER: 10959 case DOM_VK_RETURN: 10960 case DOM_VK_SPACE: 10961 if (settings.onAction) { 10962 settings.onAction(focussedId); 10963 Event.cancel(evt); 10964 } 10965 break; 10966 } 10967 }; 10968 10969 // Set up state and listeners for each item. 10970 each(items, function(item, idx) { 10971 var tabindex, elm; 10972 10973 if (!item.id) { 10974 item.id = dom.uniqueId('_mce_item_'); 10975 } 10976 10977 elm = dom.get(item.id); 10978 10979 if (excludeFromTabOrder) { 10980 dom.bind(elm, 'blur', itemBlurred); 10981 tabindex = '-1'; 10982 } else { 10983 tabindex = (idx === 0 ? '0' : '-1'); 10984 } 10985 10986 elm.setAttribute('tabindex', tabindex); 10987 dom.bind(elm, 'focus', itemFocussed); 10988 }); 10989 10990 // Setup initial state for root element. 10991 if (items[0]){ 10992 focussedId = items[0].id; 10993 } 10994 10995 dom.setAttrib(root, 'tabindex', '-1'); 10996 10997 // Setup listeners for root element. 10998 var rootElm = dom.get(root); 10999 dom.bind(rootElm, 'focus', rootFocussed); 11000 dom.bind(rootElm, 'keydown', rootKeydown); 11001 } 11002 }); 11003 })(tinymce); 11004 11005 (function(tinymce) { 11006 // Shorten class names 11007 var DOM = tinymce.DOM, is = tinymce.is; 11008 11009 tinymce.create('tinymce.ui.Control', { 11010 Control : function(id, s, editor) { 11011 this.id = id; 11012 this.settings = s = s || {}; 11013 this.rendered = false; 11014 this.onRender = new tinymce.util.Dispatcher(this); 11015 this.classPrefix = ''; 11016 this.scope = s.scope || this; 11017 this.disabled = 0; 11018 this.active = 0; 11019 this.editor = editor; 11020 }, 11021 11022 setAriaProperty : function(property, value) { 11023 var element = DOM.get(this.id + '_aria') || DOM.get(this.id); 11024 if (element) { 11025 DOM.setAttrib(element, 'aria-' + property, !!value); 11026 } 11027 }, 11028 11029 focus : function() { 11030 DOM.get(this.id).focus(); 11031 }, 11032 11033 setDisabled : function(s) { 11034 if (s != this.disabled) { 11035 this.setAriaProperty('disabled', s); 11036 11037 this.setState('Disabled', s); 11038 this.setState('Enabled', !s); 11039 this.disabled = s; 11040 } 11041 }, 11042 11043 isDisabled : function() { 11044 return this.disabled; 11045 }, 11046 11047 setActive : function(s) { 11048 if (s != this.active) { 11049 this.setState('Active', s); 11050 this.active = s; 11051 this.setAriaProperty('pressed', s); 11052 } 11053 }, 11054 11055 isActive : function() { 11056 return this.active; 11057 }, 11058 11059 setState : function(c, s) { 11060 var n = DOM.get(this.id); 11061 11062 c = this.classPrefix + c; 11063 11064 if (s) 11065 DOM.addClass(n, c); 11066 else 11067 DOM.removeClass(n, c); 11068 }, 11069 11070 isRendered : function() { 11071 return this.rendered; 11072 }, 11073 11074 renderHTML : function() { 11075 }, 11076 11077 renderTo : function(n) { 11078 DOM.setHTML(n, this.renderHTML()); 11079 }, 11080 11081 postRender : function() { 11082 var t = this, b; 11083 11084 // Set pending states 11085 if (is(t.disabled)) { 11086 b = t.disabled; 11087 t.disabled = -1; 11088 t.setDisabled(b); 11089 } 11090 11091 if (is(t.active)) { 11092 b = t.active; 11093 t.active = -1; 11094 t.setActive(b); 11095 } 11096 }, 11097 11098 remove : function() { 11099 DOM.remove(this.id); 11100 this.destroy(); 11101 }, 11102 11103 destroy : function() { 11104 tinymce.dom.Event.clear(this.id); 11105 } 11106 }); 11107 })(tinymce); 11108 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', { 11109 Container : function(id, s, editor) { 11110 this.parent(id, s, editor); 11111 11112 this.controls = []; 11113 11114 this.lookup = {}; 11115 }, 11116 11117 add : function(c) { 11118 this.lookup[c.id] = c; 11119 this.controls.push(c); 11120 11121 return c; 11122 }, 11123 11124 get : function(n) { 11125 return this.lookup[n]; 11126 } 11127 }); 11128 11129 11130 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { 11131 Separator : function(id, s) { 11132 this.parent(id, s); 11133 this.classPrefix = 'mceSeparator'; 11134 this.setDisabled(true); 11135 }, 11136 11137 renderHTML : function() { 11138 return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'}); 11139 } 11140 }); 11141 11142 (function(tinymce) { 11143 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk; 11144 11145 tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', { 11146 MenuItem : function(id, s) { 11147 this.parent(id, s); 11148 this.classPrefix = 'mceMenuItem'; 11149 }, 11150 11151 setSelected : function(s) { 11152 this.setState('Selected', s); 11153 this.setAriaProperty('checked', !!s); 11154 this.selected = s; 11155 }, 11156 11157 isSelected : function() { 11158 return this.selected; 11159 }, 11160 11161 postRender : function() { 11162 var t = this; 11163 11164 t.parent(); 11165 11166 // Set pending state 11167 if (is(t.selected)) 11168 t.setSelected(t.selected); 11169 } 11170 }); 11171 })(tinymce); 11172 11173 (function(tinymce) { 11174 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk; 11175 11176 tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', { 11177 Menu : function(id, s) { 11178 var t = this; 11179 11180 t.parent(id, s); 11181 t.items = {}; 11182 t.collapsed = false; 11183 t.menuCount = 0; 11184 t.onAddItem = new tinymce.util.Dispatcher(this); 11185 }, 11186 11187 expand : function(d) { 11188 var t = this; 11189 11190 if (d) { 11191 walk(t, function(o) { 11192 if (o.expand) 11193 o.expand(); 11194 }, 'items', t); 11195 } 11196 11197 t.collapsed = false; 11198 }, 11199 11200 collapse : function(d) { 11201 var t = this; 11202 11203 if (d) { 11204 walk(t, function(o) { 11205 if (o.collapse) 11206 o.collapse(); 11207 }, 'items', t); 11208 } 11209 11210 t.collapsed = true; 11211 }, 11212 11213 isCollapsed : function() { 11214 return this.collapsed; 11215 }, 11216 11217 add : function(o) { 11218 if (!o.settings) 11219 o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o); 11220 11221 this.onAddItem.dispatch(this, o); 11222 11223 return this.items[o.id] = o; 11224 }, 11225 11226 addSeparator : function() { 11227 return this.add({separator : true}); 11228 }, 11229 11230 addMenu : function(o) { 11231 if (!o.collapse) 11232 o = this.createMenu(o); 11233 11234 this.menuCount++; 11235 11236 return this.add(o); 11237 }, 11238 11239 hasMenus : function() { 11240 return this.menuCount !== 0; 11241 }, 11242 11243 remove : function(o) { 11244 delete this.items[o.id]; 11245 }, 11246 11247 removeAll : function() { 11248 var t = this; 11249 11250 walk(t, function(o) { 11251 if (o.removeAll) 11252 o.removeAll(); 11253 else 11254 o.remove(); 11255 11256 o.destroy(); 11257 }, 'items', t); 11258 11259 t.items = {}; 11260 }, 11261 11262 createMenu : function(o) { 11263 var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o); 11264 11265 m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem); 11266 11267 return m; 11268 } 11269 }); 11270 })(tinymce); 11271 (function(tinymce) { 11272 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element; 11273 11274 tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', { 11275 DropMenu : function(id, s) { 11276 s = s || {}; 11277 s.container = s.container || DOM.doc.body; 11278 s.offset_x = s.offset_x || 0; 11279 s.offset_y = s.offset_y || 0; 11280 s.vp_offset_x = s.vp_offset_x || 0; 11281 s.vp_offset_y = s.vp_offset_y || 0; 11282 11283 if (is(s.icons) && !s.icons) 11284 s['class'] += ' mceNoIcons'; 11285 11286 this.parent(id, s); 11287 this.onShowMenu = new tinymce.util.Dispatcher(this); 11288 this.onHideMenu = new tinymce.util.Dispatcher(this); 11289 this.classPrefix = 'mceMenu'; 11290 }, 11291 11292 createMenu : function(s) { 11293 var t = this, cs = t.settings, m; 11294 11295 s.container = s.container || cs.container; 11296 s.parent = t; 11297 s.constrain = s.constrain || cs.constrain; 11298 s['class'] = s['class'] || cs['class']; 11299 s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x; 11300 s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y; 11301 s.keyboard_focus = cs.keyboard_focus; 11302 m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s); 11303 11304 m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem); 11305 11306 return m; 11307 }, 11308 11309 focus : function() { 11310 var t = this; 11311 if (t.keyboardNav) { 11312 t.keyboardNav.focus(); 11313 } 11314 }, 11315 11316 update : function() { 11317 var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th; 11318 11319 tw = s.max_width ? Math.min(tb.offsetWidth, s.max_width) : tb.offsetWidth; 11320 th = s.max_height ? Math.min(tb.offsetHeight, s.max_height) : tb.offsetHeight; 11321 11322 if (!DOM.boxModel) 11323 t.element.setStyles({width : tw + 2, height : th + 2}); 11324 else 11325 t.element.setStyles({width : tw, height : th}); 11326 11327 if (s.max_width) 11328 DOM.setStyle(co, 'width', tw); 11329 11330 if (s.max_height) { 11331 DOM.setStyle(co, 'height', th); 11332 11333 if (tb.clientHeight < s.max_height) 11334 DOM.setStyle(co, 'overflow', 'hidden'); 11335 } 11336 }, 11337 11338 showMenu : function(x, y, px) { 11339 var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix; 11340 11341 t.collapse(1); 11342 11343 if (t.isMenuVisible) 11344 return; 11345 11346 if (!t.rendered) { 11347 co = DOM.add(t.settings.container, t.renderNode()); 11348 11349 each(t.items, function(o) { 11350 o.postRender(); 11351 }); 11352 11353 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container}); 11354 } else 11355 co = DOM.get('menu_' + t.id); 11356 11357 // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug 11358 if (!tinymce.isOpera) 11359 DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF}); 11360 11361 DOM.show(co); 11362 t.update(); 11363 11364 x += s.offset_x || 0; 11365 y += s.offset_y || 0; 11366 vp.w -= 4; 11367 vp.h -= 4; 11368 11369 // Move inside viewport if not submenu 11370 if (s.constrain) { 11371 w = co.clientWidth - ot; 11372 h = co.clientHeight - ot; 11373 mx = vp.x + vp.w; 11374 my = vp.y + vp.h; 11375 11376 if ((x + s.vp_offset_x + w) > mx) 11377 x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w); 11378 11379 if ((y + s.vp_offset_y + h) > my) 11380 y = Math.max(0, (my - s.vp_offset_y) - h); 11381 } 11382 11383 DOM.setStyles(co, {left : x , top : y}); 11384 t.element.update(); 11385 11386 t.isMenuVisible = 1; 11387 t.mouseClickFunc = Event.add(co, 'click', function(e) { 11388 var m; 11389 11390 e = e.target; 11391 11392 if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) { 11393 m = t.items[e.id]; 11394 11395 if (m.isDisabled()) 11396 return; 11397 11398 dm = t; 11399 11400 while (dm) { 11401 if (dm.hideMenu) 11402 dm.hideMenu(); 11403 11404 dm = dm.settings.parent; 11405 } 11406 11407 if (m.settings.onclick) 11408 m.settings.onclick(e); 11409 11410 return false; // Cancel to fix onbeforeunload problem 11411 } 11412 }); 11413 11414 if (t.hasMenus()) { 11415 t.mouseOverFunc = Event.add(co, 'mouseover', function(e) { 11416 var m, r, mi; 11417 11418 e = e.target; 11419 if (e && (e = DOM.getParent(e, 'tr'))) { 11420 m = t.items[e.id]; 11421 11422 if (t.lastMenu) 11423 t.lastMenu.collapse(1); 11424 11425 if (m.isDisabled()) 11426 return; 11427 11428 if (e && DOM.hasClass(e, cp + 'ItemSub')) { 11429 //p = DOM.getPos(s.container); 11430 r = DOM.getRect(e); 11431 m.showMenu((r.x + r.w - ot), r.y - ot, r.x); 11432 t.lastMenu = m; 11433 DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive'); 11434 } 11435 } 11436 }); 11437 } 11438 11439 Event.add(co, 'keydown', t._keyHandler, t); 11440 11441 t.onShowMenu.dispatch(t); 11442 11443 if (s.keyboard_focus) { 11444 t._setupKeyboardNav(); 11445 } 11446 }, 11447 11448 hideMenu : function(c) { 11449 var t = this, co = DOM.get('menu_' + t.id), e; 11450 11451 if (!t.isMenuVisible) 11452 return; 11453 11454 if (t.keyboardNav) t.keyboardNav.destroy(); 11455 Event.remove(co, 'mouseover', t.mouseOverFunc); 11456 Event.remove(co, 'click', t.mouseClickFunc); 11457 Event.remove(co, 'keydown', t._keyHandler); 11458 DOM.hide(co); 11459 t.isMenuVisible = 0; 11460 11461 if (!c) 11462 t.collapse(1); 11463 11464 if (t.element) 11465 t.element.hide(); 11466 11467 if (e = DOM.get(t.id)) 11468 DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive'); 11469 11470 t.onHideMenu.dispatch(t); 11471 }, 11472 11473 add : function(o) { 11474 var t = this, co; 11475 11476 o = t.parent(o); 11477 11478 if (t.isRendered && (co = DOM.get('menu_' + t.id))) 11479 t._add(DOM.select('tbody', co)[0], o); 11480 11481 return o; 11482 }, 11483 11484 collapse : function(d) { 11485 this.parent(d); 11486 this.hideMenu(1); 11487 }, 11488 11489 remove : function(o) { 11490 DOM.remove(o.id); 11491 this.destroy(); 11492 11493 return this.parent(o); 11494 }, 11495 11496 destroy : function() { 11497 var t = this, co = DOM.get('menu_' + t.id); 11498 11499 if (t.keyboardNav) t.keyboardNav.destroy(); 11500 Event.remove(co, 'mouseover', t.mouseOverFunc); 11501 Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc); 11502 Event.remove(co, 'click', t.mouseClickFunc); 11503 Event.remove(co, 'keydown', t._keyHandler); 11504 11505 if (t.element) 11506 t.element.remove(); 11507 11508 DOM.remove(co); 11509 }, 11510 11511 renderNode : function() { 11512 var t = this, s = t.settings, n, tb, co, w; 11513 11514 w = DOM.create('div', {role: 'listbox', id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000;outline:0'}); 11515 if (t.settings.parent) { 11516 DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id); 11517 } 11518 co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')}); 11519 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container}); 11520 11521 if (s.menu_line) 11522 DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'}); 11523 11524 // n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'}); 11525 n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0}); 11526 tb = DOM.add(n, 'tbody'); 11527 11528 each(t.items, function(o) { 11529 t._add(tb, o); 11530 }); 11531 11532 t.rendered = true; 11533 11534 return w; 11535 }, 11536 11537 // Internal functions 11538 _setupKeyboardNav : function(){ 11539 var contextMenu, menuItems, t=this; 11540 contextMenu = DOM.get('menu_' + t.id); 11541 menuItems = DOM.select('a[role=option]', 'menu_' + t.id); 11542 menuItems.splice(0,0,contextMenu); 11543 t.keyboardNav = new tinymce.ui.KeyboardNavigation({ 11544 root: 'menu_' + t.id, 11545 items: menuItems, 11546 onCancel: function() { 11547 t.hideMenu(); 11548 }, 11549 enableUpDown: true 11550 }); 11551 contextMenu.focus(); 11552 }, 11553 11554 _keyHandler : function(evt) { 11555 var t = this, e; 11556 switch (evt.keyCode) { 11557 case 37: // Left 11558 if (t.settings.parent) { 11559 t.hideMenu(); 11560 t.settings.parent.focus(); 11561 Event.cancel(evt); 11562 } 11563 break; 11564 case 39: // Right 11565 if (t.mouseOverFunc) 11566 t.mouseOverFunc(evt); 11567 break; 11568 } 11569 }, 11570 11571 _add : function(tb, o) { 11572 var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic; 11573 11574 if (s.separator) { 11575 ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'}); 11576 DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'}); 11577 11578 if (n = ro.previousSibling) 11579 DOM.addClass(n, 'mceLast'); 11580 11581 return; 11582 } 11583 11584 n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'}); 11585 n = it = DOM.add(n, s.titleItem ? 'th' : 'td'); 11586 n = a = DOM.add(n, 'a', {id: o.id + '_aria', role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'}); 11587 11588 if (s.parent) { 11589 DOM.setAttrib(a, 'aria-haspopup', 'true'); 11590 DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id); 11591 } 11592 11593 DOM.addClass(it, s['class']); 11594 // n = DOM.add(n, 'span', {'class' : 'item'}); 11595 11596 ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')}); 11597 11598 if (s.icon_src) 11599 DOM.add(ic, 'img', {src : s.icon_src}); 11600 11601 n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title); 11602 11603 if (o.settings.style) { 11604 if (typeof o.settings.style == "function") 11605 o.settings.style = o.settings.style(); 11606 11607 DOM.setAttrib(n, 'style', o.settings.style); 11608 } 11609 11610 if (tb.childNodes.length == 1) 11611 DOM.addClass(ro, 'mceFirst'); 11612 11613 if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator')) 11614 DOM.addClass(ro, 'mceFirst'); 11615 11616 if (o.collapse) 11617 DOM.addClass(ro, cp + 'ItemSub'); 11618 11619 if (n = ro.previousSibling) 11620 DOM.removeClass(n, 'mceLast'); 11621 11622 DOM.addClass(ro, 'mceLast'); 11623 } 11624 }); 11625 })(tinymce); 11626 (function(tinymce) { 11627 var DOM = tinymce.DOM; 11628 11629 tinymce.create('tinymce.ui.Button:tinymce.ui.Control', { 11630 Button : function(id, s, ed) { 11631 this.parent(id, s, ed); 11632 this.classPrefix = 'mceButton'; 11633 }, 11634 11635 renderHTML : function() { 11636 var cp = this.classPrefix, s = this.settings, h, l; 11637 11638 l = DOM.encode(s.label || ''); 11639 h = '<a role="button" id="' + this.id + '" href="javascript:;" class="' + cp + ' ' + cp + 'Enabled ' + s['class'] + (l ? ' ' + cp + 'Labeled' : '') +'" onmousedown="return false;" onclick="return false;" aria-labelledby="' + this.id + '_voice" title="' + DOM.encode(s.title) + '">'; 11640 if (s.image && !(this.editor &&this.editor.forcedHighContrastMode) ) 11641 h += '<span class="mceIcon ' + s['class'] + '"><img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" /></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : ''); 11642 else 11643 h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : ''); 11644 11645 h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>'; 11646 h += '</a>'; 11647 return h; 11648 }, 11649 11650 postRender : function() { 11651 var t = this, s = t.settings, imgBookmark; 11652 11653 // In IE a large image that occupies the entire editor area will be deselected when a button is clicked, so 11654 // need to keep the selection in case the selection is lost 11655 if (tinymce.isIE && t.editor) { 11656 tinymce.dom.Event.add(t.id, 'mousedown', function(e) { 11657 var nodeName = t.editor.selection.getNode().nodeName; 11658 imgBookmark = nodeName === 'IMG' ? t.editor.selection.getBookmark() : null; 11659 }); 11660 } 11661 tinymce.dom.Event.add(t.id, 'click', function(e) { 11662 if (!t.isDisabled()) { 11663 // restore the selection in case the selection is lost in IE 11664 if (tinymce.isIE && t.editor && imgBookmark !== null) { 11665 t.editor.selection.moveToBookmark(imgBookmark); 11666 } 11667 return s.onclick.call(s.scope, e); 11668 } 11669 }); 11670 tinymce.dom.Event.add(t.id, 'keyup', function(e) { 11671 if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR) 11672 return s.onclick.call(s.scope, e); 11673 }); 11674 } 11675 }); 11676 })(tinymce); 11677 11678 (function(tinymce) { 11679 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef; 11680 11681 tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', { 11682 ListBox : function(id, s, ed) { 11683 var t = this; 11684 11685 t.parent(id, s, ed); 11686 11687 t.items = []; 11688 11689 t.onChange = new Dispatcher(t); 11690 11691 t.onPostRender = new Dispatcher(t); 11692 11693 t.onAdd = new Dispatcher(t); 11694 11695 t.onRenderMenu = new tinymce.util.Dispatcher(this); 11696 11697 t.classPrefix = 'mceListBox'; 11698 t.marked = {}; 11699 }, 11700 11701 select : function(va) { 11702 var t = this, fv, f; 11703 11704 t.marked = {}; 11705 11706 if (va == undef) 11707 return t.selectByIndex(-1); 11708 11709 // Is string or number make function selector 11710 if (va && typeof(va)=="function") 11711 f = va; 11712 else { 11713 f = function(v) { 11714 return v == va; 11715 }; 11716 } 11717 11718 // Do we need to do something? 11719 if (va != t.selectedValue) { 11720 // Find item 11721 each(t.items, function(o, i) { 11722 if (f(o.value)) { 11723 fv = 1; 11724 t.selectByIndex(i); 11725 return false; 11726 } 11727 }); 11728 11729 if (!fv) 11730 t.selectByIndex(-1); 11731 } 11732 }, 11733 11734 selectByIndex : function(idx) { 11735 var t = this, e, o, label; 11736 11737 t.marked = {}; 11738 11739 if (idx != t.selectedIndex) { 11740 e = DOM.get(t.id + '_text'); 11741 label = DOM.get(t.id + '_voiceDesc'); 11742 o = t.items[idx]; 11743 11744 if (o) { 11745 t.selectedValue = o.value; 11746 t.selectedIndex = idx; 11747 DOM.setHTML(e, DOM.encode(o.title)); 11748 DOM.setHTML(label, t.settings.title + " - " + o.title); 11749 DOM.removeClass(e, 'mceTitle'); 11750 DOM.setAttrib(t.id, 'aria-valuenow', o.title); 11751 } else { 11752 DOM.setHTML(e, DOM.encode(t.settings.title)); 11753 DOM.setHTML(label, DOM.encode(t.settings.title)); 11754 DOM.addClass(e, 'mceTitle'); 11755 t.selectedValue = t.selectedIndex = null; 11756 DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title); 11757 } 11758 e = 0; 11759 } 11760 }, 11761 11762 mark : function(value) { 11763 this.marked[value] = true; 11764 }, 11765 11766 add : function(n, v, o) { 11767 var t = this; 11768 11769 o = o || {}; 11770 o = tinymce.extend(o, { 11771 title : n, 11772 value : v 11773 }); 11774 11775 t.items.push(o); 11776 t.onAdd.dispatch(t, o); 11777 }, 11778 11779 getLength : function() { 11780 return this.items.length; 11781 }, 11782 11783 renderHTML : function() { 11784 var h = '', t = this, s = t.settings, cp = t.classPrefix; 11785 11786 h = '<span role="listbox" aria-haspopup="true" aria-labelledby="' + t.id +'_voiceDesc" aria-describedby="' + t.id + '_voiceDesc"><table role="presentation" tabindex="0" id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>'; 11787 h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title); 11788 h += DOM.createHTML('a', {id : t.id + '_text', tabindex : -1, href : 'javascript:;', 'class' : 'mceText', onclick : "return false;", onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '</td>'; 11789 h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '<span><span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span></span>') + '</td>'; 11790 h += '</tr></tbody></table></span>'; 11791 11792 return h; 11793 }, 11794 11795 showMenu : function() { 11796 var t = this, p2, e = DOM.get(this.id), m; 11797 11798 if (t.isDisabled() || t.items.length === 0) 11799 return; 11800 11801 if (t.menu && t.menu.isMenuVisible) 11802 return t.hideMenu(); 11803 11804 if (!t.isMenuRendered) { 11805 t.renderMenu(); 11806 t.isMenuRendered = true; 11807 } 11808 11809 p2 = DOM.getPos(e); 11810 11811 m = t.menu; 11812 m.settings.offset_x = p2.x; 11813 m.settings.offset_y = p2.y; 11814 m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus 11815 11816 // Select in menu 11817 each(t.items, function(o) { 11818 if (m.items[o.id]) { 11819 m.items[o.id].setSelected(0); 11820 } 11821 }); 11822 11823 each(t.items, function(o) { 11824 if (m.items[o.id] && t.marked[o.value]) { 11825 m.items[o.id].setSelected(1); 11826 } 11827 11828 if (o.value === t.selectedValue) { 11829 m.items[o.id].setSelected(1); 11830 } 11831 }); 11832 11833 m.showMenu(0, e.clientHeight); 11834 11835 Event.add(DOM.doc, 'mousedown', t.hideMenu, t); 11836 DOM.addClass(t.id, t.classPrefix + 'Selected'); 11837 11838 //DOM.get(t.id + '_text').focus(); 11839 }, 11840 11841 hideMenu : function(e) { 11842 var t = this; 11843 11844 if (t.menu && t.menu.isMenuVisible) { 11845 DOM.removeClass(t.id, t.classPrefix + 'Selected'); 11846 11847 // Prevent double toogles by canceling the mouse click event to the button 11848 if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open')) 11849 return; 11850 11851 if (!e || !DOM.getParent(e.target, '.mceMenu')) { 11852 DOM.removeClass(t.id, t.classPrefix + 'Selected'); 11853 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); 11854 t.menu.hideMenu(); 11855 } 11856 } 11857 }, 11858 11859 renderMenu : function() { 11860 var t = this, m; 11861 11862 m = t.settings.control_manager.createDropMenu(t.id + '_menu', { 11863 menu_line : 1, 11864 'class' : t.classPrefix + 'Menu mceNoIcons', 11865 max_width : 250, 11866 max_height : 150 11867 }); 11868 11869 m.onHideMenu.add(function() { 11870 t.hideMenu(); 11871 t.focus(); 11872 }); 11873 11874 m.add({ 11875 title : t.settings.title, 11876 'class' : 'mceMenuItemTitle', 11877 onclick : function() { 11878 if (t.settings.onselect('') !== false) 11879 t.select(''); // Must be runned after 11880 } 11881 }); 11882 11883 each(t.items, function(o) { 11884 // No value then treat it as a title 11885 if (o.value === undef) { 11886 m.add({ 11887 title : o.title, 11888 role : "option", 11889 'class' : 'mceMenuItemTitle', 11890 onclick : function() { 11891 if (t.settings.onselect('') !== false) 11892 t.select(''); // Must be runned after 11893 } 11894 }); 11895 } else { 11896 o.id = DOM.uniqueId(); 11897 o.role= "option"; 11898 o.onclick = function() { 11899 if (t.settings.onselect(o.value) !== false) 11900 t.select(o.value); // Must be runned after 11901 }; 11902 11903 m.add(o); 11904 } 11905 }); 11906 11907 t.onRenderMenu.dispatch(t, m); 11908 t.menu = m; 11909 }, 11910 11911 postRender : function() { 11912 var t = this, cp = t.classPrefix; 11913 11914 Event.add(t.id, 'click', t.showMenu, t); 11915 Event.add(t.id, 'keydown', function(evt) { 11916 if (evt.keyCode == 32) { // Space 11917 t.showMenu(evt); 11918 Event.cancel(evt); 11919 } 11920 }); 11921 Event.add(t.id, 'focus', function() { 11922 if (!t._focused) { 11923 t.keyDownHandler = Event.add(t.id, 'keydown', function(e) { 11924 if (e.keyCode == 40) { 11925 t.showMenu(); 11926 Event.cancel(e); 11927 } 11928 }); 11929 t.keyPressHandler = Event.add(t.id, 'keypress', function(e) { 11930 var v; 11931 if (e.keyCode == 13) { 11932 // Fake select on enter 11933 v = t.selectedValue; 11934 t.selectedValue = null; // Needs to be null to fake change 11935 Event.cancel(e); 11936 t.settings.onselect(v); 11937 } 11938 }); 11939 } 11940 11941 t._focused = 1; 11942 }); 11943 Event.add(t.id, 'blur', function() { 11944 Event.remove(t.id, 'keydown', t.keyDownHandler); 11945 Event.remove(t.id, 'keypress', t.keyPressHandler); 11946 t._focused = 0; 11947 }); 11948 11949 // Old IE doesn't have hover on all elements 11950 if (tinymce.isIE6 || !DOM.boxModel) { 11951 Event.add(t.id, 'mouseover', function() { 11952 if (!DOM.hasClass(t.id, cp + 'Disabled')) 11953 DOM.addClass(t.id, cp + 'Hover'); 11954 }); 11955 11956 Event.add(t.id, 'mouseout', function() { 11957 if (!DOM.hasClass(t.id, cp + 'Disabled')) 11958 DOM.removeClass(t.id, cp + 'Hover'); 11959 }); 11960 } 11961 11962 t.onPostRender.dispatch(t, DOM.get(t.id)); 11963 }, 11964 11965 destroy : function() { 11966 this.parent(); 11967 11968 Event.clear(this.id + '_text'); 11969 Event.clear(this.id + '_open'); 11970 } 11971 }); 11972 })(tinymce); 11973 11974 (function(tinymce) { 11975 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef; 11976 11977 tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', { 11978 NativeListBox : function(id, s) { 11979 this.parent(id, s); 11980 this.classPrefix = 'mceNativeListBox'; 11981 }, 11982 11983 setDisabled : function(s) { 11984 DOM.get(this.id).disabled = s; 11985 this.setAriaProperty('disabled', s); 11986 }, 11987 11988 isDisabled : function() { 11989 return DOM.get(this.id).disabled; 11990 }, 11991 11992 select : function(va) { 11993 var t = this, fv, f; 11994 11995 if (va == undef) 11996 return t.selectByIndex(-1); 11997 11998 // Is string or number make function selector 11999 if (va && typeof(va)=="function") 12000 f = va; 12001 else { 12002 f = function(v) { 12003 return v == va; 12004 }; 12005 } 12006 12007 // Do we need to do something? 12008 if (va != t.selectedValue) { 12009 // Find item 12010 each(t.items, function(o, i) { 12011 if (f(o.value)) { 12012 fv = 1; 12013 t.selectByIndex(i); 12014 return false; 12015 } 12016 }); 12017 12018 if (!fv) 12019 t.selectByIndex(-1); 12020 } 12021 }, 12022 12023 selectByIndex : function(idx) { 12024 DOM.get(this.id).selectedIndex = idx + 1; 12025 this.selectedValue = this.items[idx] ? this.items[idx].value : null; 12026 }, 12027 12028 add : function(n, v, a) { 12029 var o, t = this; 12030 12031 a = a || {}; 12032 a.value = v; 12033 12034 if (t.isRendered()) 12035 DOM.add(DOM.get(this.id), 'option', a, n); 12036 12037 o = { 12038 title : n, 12039 value : v, 12040 attribs : a 12041 }; 12042 12043 t.items.push(o); 12044 t.onAdd.dispatch(t, o); 12045 }, 12046 12047 getLength : function() { 12048 return this.items.length; 12049 }, 12050 12051 renderHTML : function() { 12052 var h, t = this; 12053 12054 h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --'); 12055 12056 each(t.items, function(it) { 12057 h += DOM.createHTML('option', {value : it.value}, it.title); 12058 }); 12059 12060 h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h); 12061 h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title); 12062 return h; 12063 }, 12064 12065 postRender : function() { 12066 var t = this, ch, changeListenerAdded = true; 12067 12068 t.rendered = true; 12069 12070 function onChange(e) { 12071 var v = t.items[e.target.selectedIndex - 1]; 12072 12073 if (v && (v = v.value)) { 12074 t.onChange.dispatch(t, v); 12075 12076 if (t.settings.onselect) 12077 t.settings.onselect(v); 12078 } 12079 }; 12080 12081 Event.add(t.id, 'change', onChange); 12082 12083 // Accessibility keyhandler 12084 Event.add(t.id, 'keydown', function(e) { 12085 var bf; 12086 12087 Event.remove(t.id, 'change', ch); 12088 changeListenerAdded = false; 12089 12090 bf = Event.add(t.id, 'blur', function() { 12091 if (changeListenerAdded) return; 12092 changeListenerAdded = true; 12093 Event.add(t.id, 'change', onChange); 12094 Event.remove(t.id, 'blur', bf); 12095 }); 12096 12097 //prevent default left and right keys on chrome - so that the keyboard navigation is used. 12098 if (tinymce.isWebKit && (e.keyCode==37 ||e.keyCode==39)) { 12099 return Event.prevent(e); 12100 } 12101 12102 if (e.keyCode == 13 || e.keyCode == 32) { 12103 onChange(e); 12104 return Event.cancel(e); 12105 } 12106 }); 12107 12108 t.onPostRender.dispatch(t, DOM.get(t.id)); 12109 } 12110 }); 12111 })(tinymce); 12112 12113 (function(tinymce) { 12114 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each; 12115 12116 tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', { 12117 MenuButton : function(id, s, ed) { 12118 this.parent(id, s, ed); 12119 12120 this.onRenderMenu = new tinymce.util.Dispatcher(this); 12121 12122 s.menu_container = s.menu_container || DOM.doc.body; 12123 }, 12124 12125 showMenu : function() { 12126 var t = this, p1, p2, e = DOM.get(t.id), m; 12127 12128 if (t.isDisabled()) 12129 return; 12130 12131 if (!t.isMenuRendered) { 12132 t.renderMenu(); 12133 t.isMenuRendered = true; 12134 } 12135 12136 if (t.isMenuVisible) 12137 return t.hideMenu(); 12138 12139 p1 = DOM.getPos(t.settings.menu_container); 12140 p2 = DOM.getPos(e); 12141 12142 m = t.menu; 12143 m.settings.offset_x = p2.x; 12144 m.settings.offset_y = p2.y; 12145 m.settings.vp_offset_x = p2.x; 12146 m.settings.vp_offset_y = p2.y; 12147 m.settings.keyboard_focus = t._focused; 12148 m.showMenu(0, e.firstChild.clientHeight); 12149 12150 Event.add(DOM.doc, 'mousedown', t.hideMenu, t); 12151 t.setState('Selected', 1); 12152 12153 t.isMenuVisible = 1; 12154 }, 12155 12156 renderMenu : function() { 12157 var t = this, m; 12158 12159 m = t.settings.control_manager.createDropMenu(t.id + '_menu', { 12160 menu_line : 1, 12161 'class' : this.classPrefix + 'Menu', 12162 icons : t.settings.icons 12163 }); 12164 12165 m.onHideMenu.add(function() { 12166 t.hideMenu(); 12167 t.focus(); 12168 }); 12169 12170 t.onRenderMenu.dispatch(t, m); 12171 t.menu = m; 12172 }, 12173 12174 hideMenu : function(e) { 12175 var t = this; 12176 12177 // Prevent double toogles by canceling the mouse click event to the button 12178 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';})) 12179 return; 12180 12181 if (!e || !DOM.getParent(e.target, '.mceMenu')) { 12182 t.setState('Selected', 0); 12183 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); 12184 if (t.menu) 12185 t.menu.hideMenu(); 12186 } 12187 12188 t.isMenuVisible = 0; 12189 }, 12190 12191 postRender : function() { 12192 var t = this, s = t.settings; 12193 12194 Event.add(t.id, 'click', function() { 12195 if (!t.isDisabled()) { 12196 if (s.onclick) 12197 s.onclick(t.value); 12198 12199 t.showMenu(); 12200 } 12201 }); 12202 } 12203 }); 12204 })(tinymce); 12205 12206 (function(tinymce) { 12207 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each; 12208 12209 tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', { 12210 SplitButton : function(id, s, ed) { 12211 this.parent(id, s, ed); 12212 this.classPrefix = 'mceSplitButton'; 12213 }, 12214 12215 renderHTML : function() { 12216 var h, t = this, s = t.settings, h1; 12217 12218 h = '<tbody><tr>'; 12219 12220 if (s.image) 12221 h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']}); 12222 else 12223 h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, ''); 12224 12225 h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title); 12226 h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_action', tabindex: '-1', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>'; 12227 12228 h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>'); 12229 h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_open', tabindex: '-1', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>'; 12230 12231 h += '</tr></tbody>'; 12232 h = DOM.createHTML('table', { role: 'presentation', 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h); 12233 return DOM.createHTML('div', {id : t.id, role: 'button', tabindex: '0', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h); 12234 }, 12235 12236 postRender : function() { 12237 var t = this, s = t.settings, activate; 12238 12239 if (s.onclick) { 12240 activate = function(evt) { 12241 if (!t.isDisabled()) { 12242 s.onclick(t.value); 12243 Event.cancel(evt); 12244 } 12245 }; 12246 Event.add(t.id + '_action', 'click', activate); 12247 Event.add(t.id, ['click', 'keydown'], function(evt) { 12248 var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40; 12249 if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) { 12250 activate(); 12251 Event.cancel(evt); 12252 } else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) { 12253 t.showMenu(); 12254 Event.cancel(evt); 12255 } 12256 }); 12257 } 12258 12259 Event.add(t.id + '_open', 'click', function (evt) { 12260 t.showMenu(); 12261 Event.cancel(evt); 12262 }); 12263 Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;}); 12264 Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;}); 12265 12266 // Old IE doesn't have hover on all elements 12267 if (tinymce.isIE6 || !DOM.boxModel) { 12268 Event.add(t.id, 'mouseover', function() { 12269 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled')) 12270 DOM.addClass(t.id, 'mceSplitButtonHover'); 12271 }); 12272 12273 Event.add(t.id, 'mouseout', function() { 12274 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled')) 12275 DOM.removeClass(t.id, 'mceSplitButtonHover'); 12276 }); 12277 } 12278 }, 12279 12280 destroy : function() { 12281 this.parent(); 12282 12283 Event.clear(this.id + '_action'); 12284 Event.clear(this.id + '_open'); 12285 Event.clear(this.id); 12286 } 12287 }); 12288 })(tinymce); 12289 12290 (function(tinymce) { 12291 var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each; 12292 12293 tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', { 12294 ColorSplitButton : function(id, s, ed) { 12295 var t = this; 12296 12297 t.parent(id, s, ed); 12298 12299 t.settings = s = tinymce.extend({ 12300 colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF', 12301 grid_width : 8, 12302 default_color : '#888888' 12303 }, t.settings); 12304 12305 t.onShowMenu = new tinymce.util.Dispatcher(t); 12306 12307 t.onHideMenu = new tinymce.util.Dispatcher(t); 12308 12309 t.value = s.default_color; 12310 }, 12311 12312 showMenu : function() { 12313 var t = this, r, p, e, p2; 12314 12315 if (t.isDisabled()) 12316 return; 12317 12318 if (!t.isMenuRendered) { 12319 t.renderMenu(); 12320 t.isMenuRendered = true; 12321 } 12322 12323 if (t.isMenuVisible) 12324 return t.hideMenu(); 12325 12326 e = DOM.get(t.id); 12327 DOM.show(t.id + '_menu'); 12328 DOM.addClass(e, 'mceSplitButtonSelected'); 12329 p2 = DOM.getPos(e); 12330 DOM.setStyles(t.id + '_menu', { 12331 left : p2.x, 12332 top : p2.y + e.firstChild.clientHeight, 12333 zIndex : 200000 12334 }); 12335 e = 0; 12336 12337 Event.add(DOM.doc, 'mousedown', t.hideMenu, t); 12338 t.onShowMenu.dispatch(t); 12339 12340 if (t._focused) { 12341 t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) { 12342 if (e.keyCode == 27) 12343 t.hideMenu(); 12344 }); 12345 12346 DOM.select('a', t.id + '_menu')[0].focus(); // Select first link 12347 } 12348 12349 t.keyboardNav = new tinymce.ui.KeyboardNavigation({ 12350 root: t.id + '_menu', 12351 items: DOM.select('a', t.id + '_menu'), 12352 onCancel: function() { 12353 t.hideMenu(); 12354 t.focus(); 12355 } 12356 }); 12357 12358 t.keyboardNav.focus(); 12359 t.isMenuVisible = 1; 12360 }, 12361 12362 hideMenu : function(e) { 12363 var t = this; 12364 12365 if (t.isMenuVisible) { 12366 // Prevent double toogles by canceling the mouse click event to the button 12367 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';})) 12368 return; 12369 12370 if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) { 12371 DOM.removeClass(t.id, 'mceSplitButtonSelected'); 12372 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); 12373 Event.remove(t.id + '_menu', 'keydown', t._keyHandler); 12374 DOM.hide(t.id + '_menu'); 12375 } 12376 12377 t.isMenuVisible = 0; 12378 t.onHideMenu.dispatch(); 12379 t.keyboardNav.destroy(); 12380 } 12381 }, 12382 12383 renderMenu : function() { 12384 var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context; 12385 12386 w = DOM.add(s.menu_container, 'div', {role: 'listbox', id : t.id + '_menu', 'class' : s.menu_class + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'}); 12387 m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'}); 12388 DOM.add(m, 'span', {'class' : 'mceMenuLine'}); 12389 12390 n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'}); 12391 tb = DOM.add(n, 'tbody'); 12392 12393 // Generate color grid 12394 i = 0; 12395 each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) { 12396 c = c.replace(/^#/, ''); 12397 12398 if (!i--) { 12399 tr = DOM.add(tb, 'tr'); 12400 i = s.grid_width - 1; 12401 } 12402 12403 n = DOM.add(tr, 'td'); 12404 var settings = { 12405 href : 'javascript:;', 12406 style : { 12407 backgroundColor : '#' + c 12408 }, 12409 'title': t.editor.getLang('colors.' + c, c), 12410 'data-mce-color' : '#' + c 12411 }; 12412 12413 // adding a proper ARIA role = button causes JAWS to read things incorrectly on IE. 12414 if (!tinymce.isIE ) { 12415 settings.role = 'option'; 12416 } 12417 12418 n = DOM.add(n, 'a', settings); 12419 12420 if (t.editor.forcedHighContrastMode) { 12421 n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' }); 12422 if (n.getContext && (context = n.getContext("2d"))) { 12423 context.fillStyle = '#' + c; 12424 context.fillRect(0, 0, 16, 16); 12425 } else { 12426 // No point leaving a canvas element around if it's not supported for drawing on anyway. 12427 DOM.remove(n); 12428 } 12429 } 12430 }); 12431 12432 if (s.more_colors_func) { 12433 n = DOM.add(tb, 'tr'); 12434 n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'}); 12435 n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title); 12436 12437 Event.add(n, 'click', function(e) { 12438 s.more_colors_func.call(s.more_colors_scope || this); 12439 return Event.cancel(e); // Cancel to fix onbeforeunload problem 12440 }); 12441 } 12442 12443 DOM.addClass(m, 'mceColorSplitMenu'); 12444 12445 // Prevent IE from scrolling and hindering click to occur #4019 12446 Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);}); 12447 12448 Event.add(t.id + '_menu', 'click', function(e) { 12449 var c; 12450 12451 e = DOM.getParent(e.target, 'a', tb); 12452 12453 if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color'))) 12454 t.setColor(c); 12455 12456 return false; // Prevent IE auto save warning 12457 }); 12458 12459 return w; 12460 }, 12461 12462 setColor : function(c) { 12463 this.displayColor(c); 12464 this.hideMenu(); 12465 this.settings.onselect(c); 12466 }, 12467 12468 displayColor : function(c) { 12469 var t = this; 12470 12471 DOM.setStyle(t.id + '_preview', 'backgroundColor', c); 12472 12473 t.value = c; 12474 }, 12475 12476 postRender : function() { 12477 var t = this, id = t.id; 12478 12479 t.parent(); 12480 DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'}); 12481 DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value); 12482 }, 12483 12484 destroy : function() { 12485 var self = this; 12486 12487 self.parent(); 12488 12489 Event.clear(self.id + '_menu'); 12490 Event.clear(self.id + '_more'); 12491 DOM.remove(self.id + '_menu'); 12492 12493 if (self.keyboardNav) { 12494 self.keyboardNav.destroy(); 12495 } 12496 } 12497 }); 12498 })(tinymce); 12499 12500 (function(tinymce) { 12501 // Shorten class names 12502 var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event; 12503 tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', { 12504 renderHTML : function() { 12505 var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings; 12506 12507 h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">'); 12508 //TODO: ACC test this out - adding a role = application for getting the landmarks working well. 12509 h.push("<span role='application'>"); 12510 h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>'); 12511 each(controls, function(toolbar) { 12512 h.push(toolbar.renderHTML()); 12513 }); 12514 h.push("</span>"); 12515 h.push('</div>'); 12516 12517 return h.join(''); 12518 }, 12519 12520 focus : function() { 12521 var t = this; 12522 dom.get(t.id).focus(); 12523 }, 12524 12525 postRender : function() { 12526 var t = this, items = []; 12527 12528 each(t.controls, function(toolbar) { 12529 each (toolbar.controls, function(control) { 12530 if (control.id) { 12531 items.push(control); 12532 } 12533 }); 12534 }); 12535 12536 t.keyNav = new tinymce.ui.KeyboardNavigation({ 12537 root: t.id, 12538 items: items, 12539 onCancel: function() { 12540 //Move focus if webkit so that navigation back will read the item. 12541 if (tinymce.isWebKit) { 12542 dom.get(t.editor.id+"_ifr").focus(); 12543 } 12544 t.editor.focus(); 12545 }, 12546 excludeFromTabOrder: !t.settings.tab_focus_toolbar 12547 }); 12548 }, 12549 12550 destroy : function() { 12551 var self = this; 12552 12553 self.parent(); 12554 self.keyNav.destroy(); 12555 Event.clear(self.id); 12556 } 12557 }); 12558 })(tinymce); 12559 12560 (function(tinymce) { 12561 // Shorten class names 12562 var dom = tinymce.DOM, each = tinymce.each; 12563 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { 12564 renderHTML : function() { 12565 var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl; 12566 12567 cl = t.controls; 12568 for (i=0; i<cl.length; i++) { 12569 // Get current control, prev control, next control and if the control is a list box or not 12570 co = cl[i]; 12571 pr = cl[i - 1]; 12572 nx = cl[i + 1]; 12573 12574 // Add toolbar start 12575 if (i === 0) { 12576 c = 'mceToolbarStart'; 12577 12578 if (co.Button) 12579 c += ' mceToolbarStartButton'; 12580 else if (co.SplitButton) 12581 c += ' mceToolbarStartSplitButton'; 12582 else if (co.ListBox) 12583 c += ' mceToolbarStartListBox'; 12584 12585 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->')); 12586 } 12587 12588 // Add toolbar end before list box and after the previous button 12589 // This is to fix the o2k7 editor skins 12590 if (pr && co.ListBox) { 12591 if (pr.Button || pr.SplitButton) 12592 h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->')); 12593 } 12594 12595 // Render control HTML 12596 12597 // IE 8 quick fix, needed to propertly generate a hit area for anchors 12598 if (dom.stdMode) 12599 h += '<td style="position: relative">' + co.renderHTML() + '</td>'; 12600 else 12601 h += '<td>' + co.renderHTML() + '</td>'; 12602 12603 // Add toolbar start after list box and before the next button 12604 // This is to fix the o2k7 editor skins 12605 if (nx && co.ListBox) { 12606 if (nx.Button || nx.SplitButton) 12607 h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->')); 12608 } 12609 } 12610 12611 c = 'mceToolbarEnd'; 12612 12613 if (co.Button) 12614 c += ' mceToolbarEndButton'; 12615 else if (co.SplitButton) 12616 c += ' mceToolbarEndSplitButton'; 12617 else if (co.ListBox) 12618 c += ' mceToolbarEndListBox'; 12619 12620 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->')); 12621 12622 return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || '', role: 'presentation', tabindex: '-1'}, '<tbody><tr>' + h + '</tr></tbody>'); 12623 } 12624 }); 12625 })(tinymce); 12626 12627 (function(tinymce) { 12628 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each; 12629 12630 tinymce.create('tinymce.AddOnManager', { 12631 AddOnManager : function() { 12632 var self = this; 12633 12634 self.items = []; 12635 self.urls = {}; 12636 self.lookup = {}; 12637 self.onAdd = new Dispatcher(self); 12638 }, 12639 12640 get : function(n) { 12641 if (this.lookup[n]) { 12642 return this.lookup[n].instance; 12643 } else { 12644 return undefined; 12645 } 12646 }, 12647 12648 dependencies : function(n) { 12649 var result; 12650 if (this.lookup[n]) { 12651 result = this.lookup[n].dependencies; 12652 } 12653 return result || []; 12654 }, 12655 12656 requireLangPack : function(n) { 12657 var s = tinymce.settings; 12658 12659 if (s && s.language && s.language_load !== false) 12660 tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js'); 12661 }, 12662 12663 add : function(id, o, dependencies) { 12664 this.items.push(o); 12665 this.lookup[id] = {instance:o, dependencies:dependencies}; 12666 this.onAdd.dispatch(this, id, o); 12667 12668 return o; 12669 }, 12670 createUrl: function(baseUrl, dep) { 12671 if (typeof dep === "object") { 12672 return dep 12673 } else { 12674 return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix}; 12675 } 12676 }, 12677 12678 addComponents: function(pluginName, scripts) { 12679 var pluginUrl = this.urls[pluginName]; 12680 tinymce.each(scripts, function(script){ 12681 tinymce.ScriptLoader.add(pluginUrl+"/"+script); 12682 }); 12683 }, 12684 12685 load : function(n, u, cb, s) { 12686 var t = this, url = u; 12687 12688 function loadDependencies() { 12689 var dependencies = t.dependencies(n); 12690 tinymce.each(dependencies, function(dep) { 12691 var newUrl = t.createUrl(u, dep); 12692 t.load(newUrl.resource, newUrl, undefined, undefined); 12693 }); 12694 if (cb) { 12695 if (s) { 12696 cb.call(s); 12697 } else { 12698 cb.call(tinymce.ScriptLoader); 12699 } 12700 } 12701 } 12702 12703 if (t.urls[n]) 12704 return; 12705 if (typeof u === "object") 12706 url = u.prefix + u.resource + u.suffix; 12707 12708 if (url.indexOf('/') !== 0 && url.indexOf('://') == -1) 12709 url = tinymce.baseURL + '/' + url; 12710 12711 t.urls[n] = url.substring(0, url.lastIndexOf('/')); 12712 12713 if (t.lookup[n]) { 12714 loadDependencies(); 12715 } else { 12716 tinymce.ScriptLoader.add(url, loadDependencies, s); 12717 } 12718 } 12719 }); 12720 12721 // Create plugin and theme managers 12722 tinymce.PluginManager = new tinymce.AddOnManager(); 12723 tinymce.ThemeManager = new tinymce.AddOnManager(); 12724 }(tinymce)); 12725 12726 (function(tinymce) { 12727 // Shorten names 12728 var each = tinymce.each, extend = tinymce.extend, 12729 DOM = tinymce.DOM, Event = tinymce.dom.Event, 12730 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, 12731 explode = tinymce.explode, 12732 Dispatcher = tinymce.util.Dispatcher, undef, instanceCounter = 0; 12733 12734 // Setup some URLs where the editor API is located and where the document is 12735 tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, ''); 12736 if (!/[\/\\]$/.test(tinymce.documentBaseURL)) 12737 tinymce.documentBaseURL += '/'; 12738 12739 tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL); 12740 12741 tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL); 12742 12743 // Add before unload listener 12744 // This was required since IE was leaking memory if you added and removed beforeunload listeners 12745 // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event 12746 tinymce.onBeforeUnload = new Dispatcher(tinymce); 12747 12748 // Must be on window or IE will leak if the editor is placed in frame or iframe 12749 Event.add(window, 'beforeunload', function(e) { 12750 tinymce.onBeforeUnload.dispatch(tinymce, e); 12751 }); 12752 12753 tinymce.onAddEditor = new Dispatcher(tinymce); 12754 12755 tinymce.onRemoveEditor = new Dispatcher(tinymce); 12756 12757 tinymce.EditorManager = extend(tinymce, { 12758 editors : [], 12759 12760 i18n : {}, 12761 12762 activeEditor : null, 12763 12764 init : function(s) { 12765 var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed; 12766 12767 function createId(elm) { 12768 var id = elm.id; 12769 12770 // Use element id, or unique name or generate a unique id 12771 if (!id) { 12772 id = elm.name; 12773 12774 if (id && !DOM.get(id)) { 12775 id = elm.name; 12776 } else { 12777 // Generate unique name 12778 id = DOM.uniqueId(); 12779 } 12780 12781 elm.setAttribute('id', id); 12782 } 12783 12784 return id; 12785 }; 12786 12787 function execCallback(se, n, s) { 12788 var f = se[n]; 12789 12790 if (!f) 12791 return; 12792 12793 if (tinymce.is(f, 'string')) { 12794 s = f.replace(/\.\w+$/, ''); 12795 s = s ? tinymce.resolve(s) : 0; 12796 f = tinymce.resolve(f); 12797 } 12798 12799 return f.apply(s || this, Array.prototype.slice.call(arguments, 2)); 12800 }; 12801 12802 function hasClass(n, c) { 12803 return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c); 12804 }; 12805 12806 t.settings = s; 12807 12808 // Legacy call 12809 Event.bind(window, 'ready', function() { 12810 var l, co; 12811 12812 execCallback(s, 'onpageload'); 12813 12814 switch (s.mode) { 12815 case "exact": 12816 l = s.elements || ''; 12817 12818 if(l.length > 0) { 12819 each(explode(l), function(v) { 12820 if (DOM.get(v)) { 12821 ed = new tinymce.Editor(v, s); 12822 el.push(ed); 12823 ed.render(1); 12824 } else { 12825 each(document.forms, function(f) { 12826 each(f.elements, function(e) { 12827 if (e.name === v) { 12828 v = 'mce_editor_' + instanceCounter++; 12829 DOM.setAttrib(e, 'id', v); 12830 12831 ed = new tinymce.Editor(v, s); 12832 el.push(ed); 12833 ed.render(1); 12834 } 12835 }); 12836 }); 12837 } 12838 }); 12839 } 12840 break; 12841 12842 case "textareas": 12843 case "specific_textareas": 12844 each(DOM.select('textarea'), function(elm) { 12845 if (s.editor_deselector && hasClass(elm, s.editor_deselector)) 12846 return; 12847 12848 if (!s.editor_selector || hasClass(elm, s.editor_selector)) { 12849 ed = new tinymce.Editor(createId(elm), s); 12850 el.push(ed); 12851 ed.render(1); 12852 } 12853 }); 12854 break; 12855 12856 default: 12857 if (s.types) { 12858 // Process type specific selector 12859 each(s.types, function(type) { 12860 each(DOM.select(type.selector), function(elm) { 12861 var editor = new tinymce.Editor(createId(elm), tinymce.extend({}, s, type)); 12862 el.push(editor); 12863 editor.render(1); 12864 }); 12865 }); 12866 } else if (s.selector) { 12867 // Process global selector 12868 each(DOM.select(s.selector), function(elm) { 12869 var editor = new tinymce.Editor(createId(elm), s); 12870 el.push(editor); 12871 editor.render(1); 12872 }); 12873 } 12874 } 12875 12876 // Call onInit when all editors are initialized 12877 if (s.oninit) { 12878 l = co = 0; 12879 12880 each(el, function(ed) { 12881 co++; 12882 12883 if (!ed.initialized) { 12884 // Wait for it 12885 ed.onInit.add(function() { 12886 l++; 12887 12888 // All done 12889 if (l == co) 12890 execCallback(s, 'oninit'); 12891 }); 12892 } else 12893 l++; 12894 12895 // All done 12896 if (l == co) 12897 execCallback(s, 'oninit'); 12898 }); 12899 } 12900 }); 12901 }, 12902 12903 get : function(id) { 12904 if (id === undef) 12905 return this.editors; 12906 12907 return this.editors[id]; 12908 }, 12909 12910 getInstanceById : function(id) { 12911 return this.get(id); 12912 }, 12913 12914 add : function(editor) { 12915 var self = this, editors = self.editors; 12916 12917 // Add named and index editor instance 12918 editors[editor.id] = editor; 12919 editors.push(editor); 12920 12921 self._setActive(editor); 12922 self.onAddEditor.dispatch(self, editor); 12923 12924 12925 return editor; 12926 }, 12927 12928 remove : function(editor) { 12929 var t = this, i, editors = t.editors; 12930 12931 // Not in the collection 12932 if (!editors[editor.id]) 12933 return null; 12934 12935 delete editors[editor.id]; 12936 12937 for (i = 0; i < editors.length; i++) { 12938 if (editors[i] == editor) { 12939 editors.splice(i, 1); 12940 break; 12941 } 12942 } 12943 12944 // Select another editor since the active one was removed 12945 if (t.activeEditor == editor) 12946 t._setActive(editors[0]); 12947 12948 editor.destroy(); 12949 t.onRemoveEditor.dispatch(t, editor); 12950 12951 return editor; 12952 }, 12953 12954 execCommand : function(c, u, v) { 12955 var t = this, ed = t.get(v), w; 12956 12957 function clr() { 12958 ed.destroy(); 12959 w.detachEvent('onunload', clr); 12960 w = w.tinyMCE = w.tinymce = null; // IE leak 12961 }; 12962 12963 // Manager commands 12964 switch (c) { 12965 case "mceFocus": 12966 ed.focus(); 12967 return true; 12968 12969 case "mceAddEditor": 12970 case "mceAddControl": 12971 if (!t.get(v)) 12972 new tinymce.Editor(v, t.settings).render(); 12973 12974 return true; 12975 12976 case "mceAddFrameControl": 12977 w = v.window; 12978 12979 // Add tinyMCE global instance and tinymce namespace to specified window 12980 w.tinyMCE = tinyMCE; 12981 w.tinymce = tinymce; 12982 12983 tinymce.DOM.doc = w.document; 12984 tinymce.DOM.win = w; 12985 12986 ed = new tinymce.Editor(v.element_id, v); 12987 ed.render(); 12988 12989 // Fix IE memory leaks 12990 if (tinymce.isIE) { 12991 w.attachEvent('onunload', clr); 12992 } 12993 12994 v.page_window = null; 12995 12996 return true; 12997 12998 case "mceRemoveEditor": 12999 case "mceRemoveControl": 13000 if (ed) 13001 ed.remove(); 13002 13003 return true; 13004 13005 case 'mceToggleEditor': 13006 if (!ed) { 13007 t.execCommand('mceAddControl', 0, v); 13008 return true; 13009 } 13010 13011 if (ed.isHidden()) 13012 ed.show(); 13013 else 13014 ed.hide(); 13015 13016 return true; 13017 } 13018 13019 // Run command on active editor 13020 if (t.activeEditor) 13021 return t.activeEditor.execCommand(c, u, v); 13022 13023 return false; 13024 }, 13025 13026 execInstanceCommand : function(id, c, u, v) { 13027 var ed = this.get(id); 13028 13029 if (ed) 13030 return ed.execCommand(c, u, v); 13031 13032 return false; 13033 }, 13034 13035 triggerSave : function() { 13036 each(this.editors, function(e) { 13037 e.save(); 13038 }); 13039 }, 13040 13041 addI18n : function(p, o) { 13042 var lo, i18n = this.i18n; 13043 13044 if (!tinymce.is(p, 'string')) { 13045 each(p, function(o, lc) { 13046 each(o, function(o, g) { 13047 each(o, function(o, k) { 13048 if (g === 'common') 13049 i18n[lc + '.' + k] = o; 13050 else 13051 i18n[lc + '.' + g + '.' + k] = o; 13052 }); 13053 }); 13054 }); 13055 } else { 13056 each(o, function(o, k) { 13057 i18n[p + '.' + k] = o; 13058 }); 13059 } 13060 }, 13061 13062 // Private methods 13063 13064 _setActive : function(editor) { 13065 this.selectedInstance = this.activeEditor = editor; 13066 } 13067 }); 13068 })(tinymce); 13069 13070 (function(tinymce) { 13071 // Shorten these names 13072 var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend, 13073 each = tinymce.each, isGecko = tinymce.isGecko, 13074 isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is, 13075 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, 13076 explode = tinymce.explode; 13077 13078 tinymce.create('tinymce.Editor', { 13079 Editor : function(id, settings) { 13080 var self = this, TRUE = true; 13081 13082 self.settings = settings = extend({ 13083 id : id, 13084 language : 'en', 13085 theme : 'advanced', 13086 skin : 'default', 13087 delta_width : 0, 13088 delta_height : 0, 13089 popup_css : '', 13090 plugins : '', 13091 document_base_url : tinymce.documentBaseURL, 13092 add_form_submit_trigger : TRUE, 13093 submit_patch : TRUE, 13094 add_unload_trigger : TRUE, 13095 convert_urls : TRUE, 13096 relative_urls : TRUE, 13097 remove_script_host : TRUE, 13098 table_inline_editing : false, 13099 object_resizing : TRUE, 13100 accessibility_focus : TRUE, 13101 doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll 13102 visual : TRUE, 13103 font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large', 13104 font_size_legacy_values : 'xx-small,small,medium,large,x-large,xx-large,300%', // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size 13105 apply_source_formatting : TRUE, 13106 directionality : 'ltr', 13107 forced_root_block : 'p', 13108 hidden_input : TRUE, 13109 padd_empty_editor : TRUE, 13110 render_ui : TRUE, 13111 indentation : '30px', 13112 fix_table_elements : TRUE, 13113 inline_styles : TRUE, 13114 convert_fonts_to_spans : TRUE, 13115 indent : 'simple', 13116 indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist', 13117 indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist', 13118 validate : TRUE, 13119 entity_encoding : 'named', 13120 url_converter : self.convertURL, 13121 url_converter_scope : self, 13122 ie7_compat : TRUE 13123 }, settings); 13124 13125 self.id = self.editorId = id; 13126 13127 self.isNotDirty = false; 13128 13129 self.plugins = {}; 13130 13131 self.documentBaseURI = new tinymce.util.URI(settings.document_base_url || tinymce.documentBaseURL, { 13132 base_uri : tinyMCE.baseURI 13133 }); 13134 13135 self.baseURI = tinymce.baseURI; 13136 13137 self.contentCSS = []; 13138 13139 self.contentStyles = []; 13140 13141 // Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic 13142 self.setupEvents(); 13143 13144 // Internal command handler objects 13145 self.execCommands = {}; 13146 self.queryStateCommands = {}; 13147 self.queryValueCommands = {}; 13148 13149 // Call setup 13150 self.execCallback('setup', self); 13151 }, 13152 13153 render : function(nst) { 13154 var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader; 13155 13156 // Page is not loaded yet, wait for it 13157 if (!Event.domLoaded) { 13158 Event.add(window, 'ready', function() { 13159 t.render(); 13160 }); 13161 return; 13162 } 13163 13164 tinyMCE.settings = s; 13165 13166 // Element not found, then skip initialization 13167 if (!t.getElement()) 13168 return; 13169 13170 // Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff 13171 // here since the browser says it has contentEditable support but there is no visible caret. 13172 if (tinymce.isIDevice && !tinymce.isIOS5) 13173 return; 13174 13175 // Add hidden input for non input elements inside form elements 13176 if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form')) 13177 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id); 13178 13179 // Hide target element early to prevent content flashing 13180 if (!s.content_editable) { 13181 t.orgVisibility = t.getElement().style.visibility; 13182 t.getElement().style.visibility = 'hidden'; 13183 } 13184 13185 if (tinymce.WindowManager) 13186 t.windowManager = new tinymce.WindowManager(t); 13187 13188 if (s.encoding == 'xml') { 13189 t.onGetContent.add(function(ed, o) { 13190 if (o.save) 13191 o.content = DOM.encode(o.content); 13192 }); 13193 } 13194 13195 if (s.add_form_submit_trigger) { 13196 t.onSubmit.addToTop(function() { 13197 if (t.initialized) { 13198 t.save(); 13199 t.isNotDirty = 1; 13200 } 13201 }); 13202 } 13203 13204 if (s.add_unload_trigger) { 13205 t._beforeUnload = tinyMCE.onBeforeUnload.add(function() { 13206 if (t.initialized && !t.destroyed && !t.isHidden()) 13207 t.save({format : 'raw', no_events : true}); 13208 }); 13209 } 13210 13211 tinymce.addUnload(t.destroy, t); 13212 13213 if (s.submit_patch) { 13214 t.onBeforeRenderUI.add(function() { 13215 var n = t.getElement().form; 13216 13217 if (!n) 13218 return; 13219 13220 // Already patched 13221 if (n._mceOldSubmit) 13222 return; 13223 13224 // Check page uses id="submit" or name="submit" for it's submit button 13225 if (!n.submit.nodeType && !n.submit.length) { 13226 t.formElement = n; 13227 n._mceOldSubmit = n.submit; 13228 n.submit = function() { 13229 // Save all instances 13230 tinymce.triggerSave(); 13231 t.isNotDirty = 1; 13232 13233 return t.formElement._mceOldSubmit(t.formElement); 13234 }; 13235 } 13236 13237 n = null; 13238 }); 13239 } 13240 13241 // Load scripts 13242 function loadScripts() { 13243 if (s.language && s.language_load !== false) 13244 sl.add(tinymce.baseURL + '/langs/' + s.language + '.js'); 13245 13246 if (s.theme && typeof s.theme != "function" && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme]) 13247 ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js'); 13248 13249 each(explode(s.plugins), function(p) { 13250 if (p &&!PluginManager.urls[p]) { 13251 if (p.charAt(0) == '-') { 13252 p = p.substr(1, p.length); 13253 var dependencies = PluginManager.dependencies(p); 13254 each(dependencies, function(dep) { 13255 var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'}; 13256 dep = PluginManager.createUrl(defaultSettings, dep); 13257 PluginManager.load(dep.resource, dep); 13258 }); 13259 } else { 13260 // Skip safari plugin, since it is removed as of 3.3b1 13261 if (p == 'safari') { 13262 return; 13263 } 13264 PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'}); 13265 } 13266 } 13267 }); 13268 13269 // Init when que is loaded 13270 sl.loadQueue(function() { 13271 if (!t.removed) 13272 t.init(); 13273 }); 13274 }; 13275 13276 loadScripts(); 13277 }, 13278 13279 init : function() { 13280 var n, t = this, s = t.settings, w, h, mh, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = []; 13281 13282 tinymce.add(t); 13283 13284 s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area')); 13285 13286 if (s.theme) { 13287 if (typeof s.theme != "function") { 13288 s.theme = s.theme.replace(/-/, ''); 13289 o = ThemeManager.get(s.theme); 13290 t.theme = new o(); 13291 13292 if (t.theme.init) 13293 t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, '')); 13294 } else { 13295 t.theme = s.theme; 13296 } 13297 } 13298 13299 function initPlugin(p) { 13300 var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po; 13301 if (c && tinymce.inArray(initializedPlugins,p) === -1) { 13302 each(PluginManager.dependencies(p), function(dep){ 13303 initPlugin(dep); 13304 }); 13305 po = new c(t, u); 13306 13307 t.plugins[p] = po; 13308 13309 if (po.init) { 13310 po.init(t, u); 13311 initializedPlugins.push(p); 13312 } 13313 } 13314 } 13315 13316 // Create all plugins 13317 each(explode(s.plugins.replace(/\-/g, '')), initPlugin); 13318 13319 // Setup popup CSS path(s) 13320 if (s.popup_css !== false) { 13321 if (s.popup_css) 13322 s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css); 13323 else 13324 s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css"); 13325 } 13326 13327 if (s.popup_css_add) 13328 s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add); 13329 13330 t.controlManager = new tinymce.ControlManager(t); 13331 13332 // Enables users to override the control factory 13333 t.onBeforeRenderUI.dispatch(t, t.controlManager); 13334 13335 // Measure box 13336 if (s.render_ui && t.theme) { 13337 t.orgDisplay = e.style.display; 13338 13339 if (typeof s.theme != "function") { 13340 w = s.width || e.style.width || e.offsetWidth; 13341 h = s.height || e.style.height || e.offsetHeight; 13342 mh = s.min_height || 100; 13343 re = /^[0-9\.]+(|px)$/i; 13344 13345 if (re.test('' + w)) 13346 w = Math.max(parseInt(w, 10) + (o.deltaWidth || 0), 100); 13347 13348 if (re.test('' + h)) 13349 h = Math.max(parseInt(h, 10) + (o.deltaHeight || 0), mh); 13350 13351 // Render UI 13352 o = t.theme.renderUI({ 13353 targetNode : e, 13354 width : w, 13355 height : h, 13356 deltaWidth : s.delta_width, 13357 deltaHeight : s.delta_height 13358 }); 13359 13360 // Resize editor 13361 DOM.setStyles(o.sizeContainer || o.editorContainer, { 13362 width : w, 13363 height : h 13364 }); 13365 13366 h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : ''); 13367 if (h < mh) 13368 h = mh; 13369 } else { 13370 o = s.theme(t, e); 13371 13372 // Convert element type to id:s 13373 if (o.editorContainer.nodeType) { 13374 o.editorContainer = o.editorContainer.id = o.editorContainer.id || t.id + "_parent"; 13375 } 13376 13377 // Convert element type to id:s 13378 if (o.iframeContainer.nodeType) { 13379 o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || t.id + "_iframecontainer"; 13380 } 13381 13382 // Use specified iframe height or the targets offsetHeight 13383 h = o.iframeHeight || e.offsetHeight; 13384 13385 // Store away the selection when it's changed to it can be restored later with a editor.focus() call 13386 if (isIE) { 13387 t.onInit.add(function(ed) { 13388 ed.dom.bind(ed.getBody(), 'beforedeactivate keydown', function() { 13389 ed.lastIERng = ed.selection.getRng(); 13390 }); 13391 }); 13392 } 13393 } 13394 13395 t.editorContainer = o.editorContainer; 13396 } 13397 13398 // Load specified content CSS last 13399 if (s.content_css) { 13400 each(explode(s.content_css), function(u) { 13401 t.contentCSS.push(t.documentBaseURI.toAbsolute(u)); 13402 }); 13403 } 13404 13405 // Content editable mode ends here 13406 if (s.content_editable) { 13407 e = n = o = null; // Fix IE leak 13408 return t.initContentBody(); 13409 } 13410 13411 // User specified a document.domain value 13412 if (document.domain && location.hostname != document.domain) 13413 tinymce.relaxedDomain = document.domain; 13414 13415 t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">'; 13416 13417 // We only need to override paths if we have to 13418 // IE has a bug where it remove site absolute urls to relative ones if this is specified 13419 if (s.document_base_url != tinymce.documentBaseURL) 13420 t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />'; 13421 13422 // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode. 13423 if (s.ie7_compat) 13424 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />'; 13425 else 13426 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />'; 13427 13428 t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />'; 13429 13430 // Load the CSS by injecting them into the HTML this will reduce "flicker" 13431 for (i = 0; i < t.contentCSS.length; i++) { 13432 t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />'; 13433 } 13434 13435 t.contentCSS = []; 13436 13437 bi = s.body_id || 'tinymce'; 13438 if (bi.indexOf('=') != -1) { 13439 bi = t.getParam('body_id', '', 'hash'); 13440 bi = bi[t.id] || bi; 13441 } 13442 13443 bc = s.body_class || ''; 13444 if (bc.indexOf('=') != -1) { 13445 bc = t.getParam('body_class', '', 'hash'); 13446 bc = bc[t.id] || ''; 13447 } 13448 13449 t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '" onload="window.parent.tinyMCE.get(\'' + t.id + '\').onLoad.dispatch();"><br></body></html>'; 13450 13451 // Domain relaxing enabled, then set document domain 13452 if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) { 13453 // We need to write the contents here in IE since multiple writes messes up refresh button and back button 13454 u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.initContentBody();})()'; 13455 } 13456 13457 // Create iframe 13458 // TODO: ACC add the appropriate description on this. 13459 n = DOM.add(o.iframeContainer, 'iframe', { 13460 id : t.id + "_ifr", 13461 src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7 13462 frameBorder : '0', 13463 allowTransparency : "true", 13464 title : s.aria_label, 13465 style : { 13466 width : '100%', 13467 height : h, 13468 display : 'block' // Important for Gecko to render the iframe correctly 13469 } 13470 }); 13471 13472 t.contentAreaContainer = o.iframeContainer; 13473 13474 if (o.editorContainer) { 13475 DOM.get(o.editorContainer).style.display = t.orgDisplay; 13476 } 13477 13478 // Restore visibility on target element 13479 e.style.visibility = t.orgVisibility; 13480 13481 DOM.get(t.id).style.display = 'none'; 13482 DOM.setAttrib(t.id, 'aria-hidden', true); 13483 13484 if (!tinymce.relaxedDomain || !u) 13485 t.initContentBody(); 13486 13487 e = n = o = null; // Cleanup 13488 }, 13489 13490 initContentBody : function() { 13491 var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), html, body, contentCssText; 13492 13493 // Setup iframe body 13494 if ((!isIE || !tinymce.relaxedDomain) && !settings.content_editable) { 13495 doc.open(); 13496 doc.write(self.iframeHTML); 13497 doc.close(); 13498 13499 if (tinymce.relaxedDomain) 13500 doc.domain = tinymce.relaxedDomain; 13501 } 13502 13503 if (settings.content_editable) { 13504 DOM.addClass(targetElm, 'mceContentBody'); 13505 self.contentDocument = doc = settings.content_document || document; 13506 self.contentWindow = settings.content_window || window; 13507 self.bodyElement = targetElm; 13508 13509 // Prevent leak in IE 13510 settings.content_document = settings.content_window = null; 13511 } 13512 13513 // It will not steal focus while setting contentEditable 13514 body = self.getBody(); 13515 body.disabled = true; 13516 13517 if (!settings.readonly) 13518 body.contentEditable = self.getParam('content_editable_state', true); 13519 13520 body.disabled = false; 13521 13522 self.schema = new tinymce.html.Schema(settings); 13523 13524 self.dom = new tinymce.dom.DOMUtils(doc, { 13525 keep_values : true, 13526 url_converter : self.convertURL, 13527 url_converter_scope : self, 13528 hex_colors : settings.force_hex_style_colors, 13529 class_filter : settings.class_filter, 13530 update_styles : true, 13531 root_element : settings.content_editable ? self.id : null, 13532 schema : self.schema 13533 }); 13534 13535 self.parser = new tinymce.html.DomParser(settings, self.schema); 13536 13537 // Convert src and href into data-mce-src, data-mce-href and data-mce-style 13538 self.parser.addAttributeFilter('src,href,style', function(nodes, name) { 13539 var i = nodes.length, node, dom = self.dom, value, internalName; 13540 13541 while (i--) { 13542 node = nodes[i]; 13543 value = node.attr(name); 13544 internalName = 'data-mce-' + name; 13545 13546 // Add internal attribute if we need to we don't on a refresh of the document 13547 if (!node.attributes.map[internalName]) { 13548 if (name === "style") 13549 node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name)); 13550 else 13551 node.attr(internalName, self.convertURL(value, name, node.name)); 13552 } 13553 } 13554 }); 13555 13556 // Keep scripts from executing 13557 self.parser.addNodeFilter('script', function(nodes, name) { 13558 var i = nodes.length, node; 13559 13560 while (i--) { 13561 node = nodes[i]; 13562 node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript')); 13563 } 13564 }); 13565 13566 self.parser.addNodeFilter('#cdata', function(nodes, name) { 13567 var i = nodes.length, node; 13568 13569 while (i--) { 13570 node = nodes[i]; 13571 node.type = 8; 13572 node.name = '#comment'; 13573 node.value = '[CDATA[' + node.value + ']]'; 13574 } 13575 }); 13576 13577 self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) { 13578 var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements(); 13579 13580 while (i--) { 13581 node = nodes[i]; 13582 13583 if (node.isEmpty(nonEmptyElements)) 13584 node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true; 13585 } 13586 }); 13587 13588 self.serializer = new tinymce.dom.Serializer(settings, self.dom, self.schema); 13589 13590 self.selection = new tinymce.dom.Selection(self.dom, self.getWin(), self.serializer, self); 13591 13592 self.formatter = new tinymce.Formatter(self); 13593 13594 self.undoManager = new tinymce.UndoManager(self); 13595 13596 self.forceBlocks = new tinymce.ForceBlocks(self); 13597 self.enterKey = new tinymce.EnterKey(self); 13598 self.editorCommands = new tinymce.EditorCommands(self); 13599 13600 self.onExecCommand.add(function(editor, command) { 13601 // Don't refresh the select lists until caret move 13602 if (!/^(FontName|FontSize)$/.test(command)) 13603 self.nodeChanged(); 13604 }); 13605 13606 // Pass through 13607 self.serializer.onPreProcess.add(function(se, o) { 13608 return self.onPreProcess.dispatch(self, o, se); 13609 }); 13610 13611 self.serializer.onPostProcess.add(function(se, o) { 13612 return self.onPostProcess.dispatch(self, o, se); 13613 }); 13614 13615 self.onPreInit.dispatch(self); 13616 13617 if (!settings.browser_spellcheck && !settings.gecko_spellcheck) 13618 doc.body.spellcheck = false; 13619 13620 if (!settings.readonly) { 13621 self.bindNativeEvents(); 13622 } 13623 13624 self.controlManager.onPostRender.dispatch(self, self.controlManager); 13625 self.onPostRender.dispatch(self); 13626 13627 self.quirks = tinymce.util.Quirks(self); 13628 13629 if (settings.directionality) 13630 body.dir = settings.directionality; 13631 13632 if (settings.nowrap) 13633 body.style.whiteSpace = "nowrap"; 13634 13635 if (settings.protect) { 13636 self.onBeforeSetContent.add(function(ed, o) { 13637 each(settings.protect, function(pattern) { 13638 o.content = o.content.replace(pattern, function(str) { 13639 return '<!--mce:protected ' + escape(str) + '-->'; 13640 }); 13641 }); 13642 }); 13643 } 13644 13645 // Add visual aids when new contents is added 13646 self.onSetContent.add(function() { 13647 self.addVisual(self.getBody()); 13648 }); 13649 13650 // Remove empty contents 13651 if (settings.padd_empty_editor) { 13652 self.onPostProcess.add(function(ed, o) { 13653 o.content = o.content.replace(/^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, ''); 13654 }); 13655 } 13656 13657 self.load({initial : true, format : 'html'}); 13658 self.startContent = self.getContent({format : 'raw'}); 13659 13660 self.initialized = true; 13661 13662 self.onInit.dispatch(self); 13663 self.execCallback('setupcontent_callback', self.id, body, doc); 13664 self.execCallback('init_instance_callback', self); 13665 self.focus(true); 13666 self.nodeChanged({initial : true}); 13667 13668 // Add editor specific CSS styles 13669 if (self.contentStyles.length > 0) { 13670 contentCssText = ''; 13671 13672 each(self.contentStyles, function(style) { 13673 contentCssText += style + "\r\n"; 13674 }); 13675 13676 self.dom.addStyle(contentCssText); 13677 } 13678 13679 // Load specified content CSS last 13680 each(self.contentCSS, function(url) { 13681 self.dom.loadCSS(url); 13682 }); 13683 13684 // Handle auto focus 13685 if (settings.auto_focus) { 13686 setTimeout(function () { 13687 var ed = tinymce.get(settings.auto_focus); 13688 13689 ed.selection.select(ed.getBody(), 1); 13690 ed.selection.collapse(1); 13691 ed.getBody().focus(); 13692 ed.getWin().focus(); 13693 }, 100); 13694 } 13695 13696 // Clean up references for IE 13697 targetElm = doc = body = null; 13698 }, 13699 13700 focus : function(skip_focus) { 13701 var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, ieRng, controlElm, doc = self.getDoc(), body; 13702 13703 if (!skip_focus) { 13704 if (self.lastIERng) { 13705 selection.setRng(self.lastIERng); 13706 } 13707 13708 // Get selected control element 13709 ieRng = selection.getRng(); 13710 if (ieRng.item) { 13711 controlElm = ieRng.item(0); 13712 } 13713 13714 self._refreshContentEditable(); 13715 13716 // Focus the window iframe 13717 if (!contentEditable) { 13718 self.getWin().focus(); 13719 } 13720 13721 // Focus the body as well since it's contentEditable 13722 if (tinymce.isGecko || contentEditable) { 13723 body = self.getBody(); 13724 13725 // Check for setActive since it doesn't scroll to the element 13726 if (body.setActive) { 13727 body.setActive(); 13728 } else { 13729 body.focus(); 13730 } 13731 13732 if (contentEditable) { 13733 selection.normalize(); 13734 } 13735 } 13736 13737 // Restore selected control element 13738 // This is needed when for example an image is selected within a 13739 // layer a call to focus will then remove the control selection 13740 if (controlElm && controlElm.ownerDocument == doc) { 13741 ieRng = doc.body.createControlRange(); 13742 ieRng.addElement(controlElm); 13743 ieRng.select(); 13744 } 13745 } 13746 13747 if (tinymce.activeEditor != self) { 13748 if ((oed = tinymce.activeEditor) != null) 13749 oed.onDeactivate.dispatch(oed, self); 13750 13751 self.onActivate.dispatch(self, oed); 13752 } 13753 13754 tinymce._setActive(self); 13755 }, 13756 13757 execCallback : function(n) { 13758 var t = this, f = t.settings[n], s; 13759 13760 if (!f) 13761 return; 13762 13763 // Look through lookup 13764 if (t.callbackLookup && (s = t.callbackLookup[n])) { 13765 f = s.func; 13766 s = s.scope; 13767 } 13768 13769 if (is(f, 'string')) { 13770 s = f.replace(/\.\w+$/, ''); 13771 s = s ? tinymce.resolve(s) : 0; 13772 f = tinymce.resolve(f); 13773 t.callbackLookup = t.callbackLookup || {}; 13774 t.callbackLookup[n] = {func : f, scope : s}; 13775 } 13776 13777 return f.apply(s || t, Array.prototype.slice.call(arguments, 1)); 13778 }, 13779 13780 translate : function(s) { 13781 var c = this.settings.language || 'en', i18n = tinymce.i18n; 13782 13783 if (!s) 13784 return ''; 13785 13786 return i18n[c + '.' + s] || s.replace(/\{\#([^\}]+)\}/g, function(a, b) { 13787 return i18n[c + '.' + b] || '{#' + b + '}'; 13788 }); 13789 }, 13790 13791 getLang : function(n, dv) { 13792 return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}'); 13793 }, 13794 13795 getParam : function(n, dv, ty) { 13796 var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o; 13797 13798 if (ty === 'hash') { 13799 o = {}; 13800 13801 if (is(v, 'string')) { 13802 each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) { 13803 v = v.split('='); 13804 13805 if (v.length > 1) 13806 o[tr(v[0])] = tr(v[1]); 13807 else 13808 o[tr(v[0])] = tr(v); 13809 }); 13810 } else 13811 o = v; 13812 13813 return o; 13814 } 13815 13816 return v; 13817 }, 13818 13819 nodeChanged : function(o) { 13820 var self = this, selection = self.selection, node; 13821 13822 // Fix for bug #1896577 it seems that this can not be fired while the editor is loading 13823 if (self.initialized) { 13824 o = o || {}; 13825 13826 // Get start node 13827 node = selection.getStart() || self.getBody(); 13828 node = isIE && node.ownerDocument != self.getDoc() ? self.getBody() : node; // Fix for IE initial state 13829 13830 // Get parents and add them to object 13831 o.parents = []; 13832 self.dom.getParent(node, function(node) { 13833 if (node.nodeName == 'BODY') 13834 return true; 13835 13836 o.parents.push(node); 13837 }); 13838 13839 self.onNodeChange.dispatch( 13840 self, 13841 o ? o.controlManager || self.controlManager : self.controlManager, 13842 node, 13843 selection.isCollapsed(), 13844 o 13845 ); 13846 } 13847 }, 13848 13849 addButton : function(name, settings) { 13850 var self = this; 13851 13852 self.buttons = self.buttons || {}; 13853 self.buttons[name] = settings; 13854 }, 13855 13856 addCommand : function(name, callback, scope) { 13857 this.execCommands[name] = {func : callback, scope : scope || this}; 13858 }, 13859 13860 addQueryStateHandler : function(name, callback, scope) { 13861 this.queryStateCommands[name] = {func : callback, scope : scope || this}; 13862 }, 13863 13864 addQueryValueHandler : function(name, callback, scope) { 13865 this.queryValueCommands[name] = {func : callback, scope : scope || this}; 13866 }, 13867 13868 addShortcut : function(pa, desc, cmd_func, sc) { 13869 var t = this, c; 13870 13871 if (t.settings.custom_shortcuts === false) 13872 return false; 13873 13874 t.shortcuts = t.shortcuts || {}; 13875 13876 if (is(cmd_func, 'string')) { 13877 c = cmd_func; 13878 13879 cmd_func = function() { 13880 t.execCommand(c, false, null); 13881 }; 13882 } 13883 13884 if (is(cmd_func, 'object')) { 13885 c = cmd_func; 13886 13887 cmd_func = function() { 13888 t.execCommand(c[0], c[1], c[2]); 13889 }; 13890 } 13891 13892 each(explode(pa), function(pa) { 13893 var o = { 13894 func : cmd_func, 13895 scope : sc || this, 13896 desc : t.translate(desc), 13897 alt : false, 13898 ctrl : false, 13899 shift : false 13900 }; 13901 13902 each(explode(pa, '+'), function(v) { 13903 switch (v) { 13904 case 'alt': 13905 case 'ctrl': 13906 case 'shift': 13907 o[v] = true; 13908 break; 13909 13910 default: 13911 o.charCode = v.charCodeAt(0); 13912 o.keyCode = v.toUpperCase().charCodeAt(0); 13913 } 13914 }); 13915 13916 t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o; 13917 }); 13918 13919 return true; 13920 }, 13921 13922 execCommand : function(cmd, ui, val, a) { 13923 var t = this, s = 0, o, st; 13924 13925 if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus)) 13926 t.focus(); 13927 13928 a = extend({}, a); 13929 t.onBeforeExecCommand.dispatch(t, cmd, ui, val, a); 13930 if (a.terminate) 13931 return false; 13932 13933 // Command callback 13934 if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) { 13935 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13936 return true; 13937 } 13938 13939 // Registred commands 13940 if (o = t.execCommands[cmd]) { 13941 st = o.func.call(o.scope, ui, val); 13942 13943 // Fall through on true 13944 if (st !== true) { 13945 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13946 return st; 13947 } 13948 } 13949 13950 // Plugin commands 13951 each(t.plugins, function(p) { 13952 if (p.execCommand && p.execCommand(cmd, ui, val)) { 13953 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13954 s = 1; 13955 return false; 13956 } 13957 }); 13958 13959 if (s) 13960 return true; 13961 13962 // Theme commands 13963 if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) { 13964 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13965 return true; 13966 } 13967 13968 // Editor commands 13969 if (t.editorCommands.execCommand(cmd, ui, val)) { 13970 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13971 return true; 13972 } 13973 13974 // Browser commands 13975 t.getDoc().execCommand(cmd, ui, val); 13976 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13977 }, 13978 13979 queryCommandState : function(cmd) { 13980 var t = this, o, s; 13981 13982 // Is hidden then return undefined 13983 if (t._isHidden()) 13984 return; 13985 13986 // Registred commands 13987 if (o = t.queryStateCommands[cmd]) { 13988 s = o.func.call(o.scope); 13989 13990 // Fall though on true 13991 if (s !== true) 13992 return s; 13993 } 13994 13995 // Registred commands 13996 o = t.editorCommands.queryCommandState(cmd); 13997 if (o !== -1) 13998 return o; 13999 14000 // Browser commands 14001 try { 14002 return this.getDoc().queryCommandState(cmd); 14003 } catch (ex) { 14004 // Fails sometimes see bug: 1896577 14005 } 14006 }, 14007 14008 queryCommandValue : function(c) { 14009 var t = this, o, s; 14010 14011 // Is hidden then return undefined 14012 if (t._isHidden()) 14013 return; 14014 14015 // Registred commands 14016 if (o = t.queryValueCommands[c]) { 14017 s = o.func.call(o.scope); 14018 14019 // Fall though on true 14020 if (s !== true) 14021 return s; 14022 } 14023 14024 // Registred commands 14025 o = t.editorCommands.queryCommandValue(c); 14026 if (is(o)) 14027 return o; 14028 14029 // Browser commands 14030 try { 14031 return this.getDoc().queryCommandValue(c); 14032 } catch (ex) { 14033 // Fails sometimes see bug: 1896577 14034 } 14035 }, 14036 14037 show : function() { 14038 var self = this; 14039 14040 DOM.show(self.getContainer()); 14041 DOM.hide(self.id); 14042 self.load(); 14043 }, 14044 14045 hide : function() { 14046 var self = this, doc = self.getDoc(); 14047 14048 // Fixed bug where IE has a blinking cursor left from the editor 14049 if (isIE && doc) 14050 doc.execCommand('SelectAll'); 14051 14052 // We must save before we hide so Safari doesn't crash 14053 self.save(); 14054 DOM.hide(self.getContainer()); 14055 DOM.setStyle(self.id, 'display', self.orgDisplay); 14056 }, 14057 14058 isHidden : function() { 14059 return !DOM.isHidden(this.id); 14060 }, 14061 14062 setProgressState : function(b, ti, o) { 14063 this.onSetProgressState.dispatch(this, b, ti, o); 14064 14065 return b; 14066 }, 14067 14068 load : function(o) { 14069 var t = this, e = t.getElement(), h; 14070 14071 if (e) { 14072 o = o || {}; 14073 o.load = true; 14074 14075 // Double encode existing entities in the value 14076 h = t.setContent(is(e.value) ? e.value : e.innerHTML, o); 14077 o.element = e; 14078 14079 if (!o.no_events) 14080 t.onLoadContent.dispatch(t, o); 14081 14082 o.element = e = null; 14083 14084 return h; 14085 } 14086 }, 14087 14088 save : function(o) { 14089 var t = this, e = t.getElement(), h, f; 14090 14091 if (!e || !t.initialized) 14092 return; 14093 14094 o = o || {}; 14095 o.save = true; 14096 14097 o.element = e; 14098 h = o.content = t.getContent(o); 14099 14100 if (!o.no_events) 14101 t.onSaveContent.dispatch(t, o); 14102 14103 h = o.content; 14104 14105 if (!/TEXTAREA|INPUT/i.test(e.nodeName)) { 14106 e.innerHTML = h; 14107 14108 // Update hidden form element 14109 if (f = DOM.getParent(t.id, 'form')) { 14110 each(f.elements, function(e) { 14111 if (e.name == t.id) { 14112 e.value = h; 14113 return false; 14114 } 14115 }); 14116 } 14117 } else 14118 e.value = h; 14119 14120 o.element = e = null; 14121 14122 return h; 14123 }, 14124 14125 setContent : function(content, args) { 14126 var self = this, rootNode, body = self.getBody(), forcedRootBlockName; 14127 14128 // Setup args object 14129 args = args || {}; 14130 args.format = args.format || 'html'; 14131 args.set = true; 14132 args.content = content; 14133 14134 // Do preprocessing 14135 if (!args.no_events) 14136 self.onBeforeSetContent.dispatch(self, args); 14137 14138 content = args.content; 14139 14140 // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content 14141 // It will also be impossible to place the caret in the editor unless there is a BR element present 14142 if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) { 14143 forcedRootBlockName = self.settings.forced_root_block; 14144 if (forcedRootBlockName) 14145 content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>'; 14146 else 14147 content = '<br data-mce-bogus="1">'; 14148 14149 body.innerHTML = content; 14150 self.selection.select(body, true); 14151 self.selection.collapse(true); 14152 return; 14153 } 14154 14155 // Parse and serialize the html 14156 if (args.format !== 'raw') { 14157 content = new tinymce.html.Serializer({}, self.schema).serialize( 14158 self.parser.parse(content) 14159 ); 14160 } 14161 14162 // Set the new cleaned contents to the editor 14163 args.content = tinymce.trim(content); 14164 self.dom.setHTML(body, args.content); 14165 14166 // Do post processing 14167 if (!args.no_events) 14168 self.onSetContent.dispatch(self, args); 14169 14170 // Don't normalize selection if the focused element isn't the body in content editable mode since it will steal focus otherwise 14171 if (!self.settings.content_editable || document.activeElement === self.getBody()) { 14172 self.selection.normalize(); 14173 } 14174 14175 return args.content; 14176 }, 14177 14178 getContent : function(args) { 14179 var self = this, content; 14180 14181 // Setup args object 14182 args = args || {}; 14183 args.format = args.format || 'html'; 14184 args.get = true; 14185 args.getInner = true; 14186 14187 // Do preprocessing 14188 if (!args.no_events) 14189 self.onBeforeGetContent.dispatch(self, args); 14190 14191 // Get raw contents or by default the cleaned contents 14192 if (args.format == 'raw') 14193 content = self.getBody().innerHTML; 14194 else 14195 content = self.serializer.serialize(self.getBody(), args); 14196 14197 args.content = tinymce.trim(content); 14198 14199 // Do post processing 14200 if (!args.no_events) 14201 self.onGetContent.dispatch(self, args); 14202 14203 return args.content; 14204 }, 14205 14206 isDirty : function() { 14207 var self = this; 14208 14209 return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty; 14210 }, 14211 14212 getContainer : function() { 14213 var self = this; 14214 14215 if (!self.container) 14216 self.container = DOM.get(self.editorContainer || self.id + '_parent'); 14217 14218 return self.container; 14219 }, 14220 14221 getContentAreaContainer : function() { 14222 return this.contentAreaContainer; 14223 }, 14224 14225 getElement : function() { 14226 return DOM.get(this.settings.content_element || this.id); 14227 }, 14228 14229 getWin : function() { 14230 var self = this, elm; 14231 14232 if (!self.contentWindow) { 14233 elm = DOM.get(self.id + "_ifr"); 14234 14235 if (elm) 14236 self.contentWindow = elm.contentWindow; 14237 } 14238 14239 return self.contentWindow; 14240 }, 14241 14242 getDoc : function() { 14243 var self = this, win; 14244 14245 if (!self.contentDocument) { 14246 win = self.getWin(); 14247 14248 if (win) 14249 self.contentDocument = win.document; 14250 } 14251 14252 return self.contentDocument; 14253 }, 14254 14255 getBody : function() { 14256 return this.bodyElement || this.getDoc().body; 14257 }, 14258 14259 convertURL : function(url, name, elm) { 14260 var self = this, settings = self.settings; 14261 14262 // Use callback instead 14263 if (settings.urlconverter_callback) 14264 return self.execCallback('urlconverter_callback', url, elm, true, name); 14265 14266 // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs 14267 if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0) 14268 return url; 14269 14270 // Convert to relative 14271 if (settings.relative_urls) 14272 return self.documentBaseURI.toRelative(url); 14273 14274 // Convert to absolute 14275 url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host); 14276 14277 return url; 14278 }, 14279 14280 addVisual : function(elm) { 14281 var self = this, settings = self.settings, dom = self.dom, cls; 14282 14283 elm = elm || self.getBody(); 14284 14285 if (!is(self.hasVisual)) 14286 self.hasVisual = settings.visual; 14287 14288 each(dom.select('table,a', elm), function(elm) { 14289 var value; 14290 14291 switch (elm.nodeName) { 14292 case 'TABLE': 14293 cls = settings.visual_table_class || 'mceItemTable'; 14294 value = dom.getAttrib(elm, 'border'); 14295 14296 if (!value || value == '0') { 14297 if (self.hasVisual) 14298 dom.addClass(elm, cls); 14299 else 14300 dom.removeClass(elm, cls); 14301 } 14302 14303 return; 14304 14305 case 'A': 14306 if (!dom.getAttrib(elm, 'href', false)) { 14307 value = dom.getAttrib(elm, 'name') || elm.id; 14308 cls = 'mceItemAnchor'; 14309 14310 if (value) { 14311 if (self.hasVisual) 14312 dom.addClass(elm, cls); 14313 else 14314 dom.removeClass(elm, cls); 14315 } 14316 } 14317 14318 return; 14319 } 14320 }); 14321 14322 self.onVisualAid.dispatch(self, elm, self.hasVisual); 14323 }, 14324 14325 remove : function() { 14326 var self = this, elm = self.getContainer(); 14327 14328 if (!self.removed) { 14329 self.removed = 1; // Cancels post remove event execution 14330 self.hide(); 14331 14332 // Don't clear the window or document if content editable 14333 // is enabled since other instances might still be present 14334 if (!self.settings.content_editable) { 14335 Event.unbind(self.getWin()); 14336 Event.unbind(self.getDoc()); 14337 } 14338 14339 Event.unbind(self.getBody()); 14340 Event.clear(elm); 14341 14342 self.execCallback('remove_instance_callback', self); 14343 self.onRemove.dispatch(self); 14344 14345 // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command 14346 self.onExecCommand.listeners = []; 14347 14348 tinymce.remove(self); 14349 DOM.remove(elm); 14350 } 14351 }, 14352 14353 destroy : function(s) { 14354 var t = this; 14355 14356 // One time is enough 14357 if (t.destroyed) 14358 return; 14359 14360 // We must unbind on Gecko since it would otherwise produce the pesky "attempt to run compile-and-go script on a cleared scope" message 14361 if (isGecko) { 14362 Event.unbind(t.getDoc()); 14363 Event.unbind(t.getWin()); 14364 Event.unbind(t.getBody()); 14365 } 14366 14367 if (!s) { 14368 tinymce.removeUnload(t.destroy); 14369 tinyMCE.onBeforeUnload.remove(t._beforeUnload); 14370 14371 // Manual destroy 14372 if (t.theme && t.theme.destroy) 14373 t.theme.destroy(); 14374 14375 // Destroy controls, selection and dom 14376 t.controlManager.destroy(); 14377 t.selection.destroy(); 14378 t.dom.destroy(); 14379 } 14380 14381 if (t.formElement) { 14382 t.formElement.submit = t.formElement._mceOldSubmit; 14383 t.formElement._mceOldSubmit = null; 14384 } 14385 14386 t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null; 14387 14388 if (t.selection) 14389 t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null; 14390 14391 t.destroyed = 1; 14392 }, 14393 14394 // Internal functions 14395 14396 _refreshContentEditable : function() { 14397 var self = this, body, parent; 14398 14399 // Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again 14400 if (self._isHidden()) { 14401 body = self.getBody(); 14402 parent = body.parentNode; 14403 14404 parent.removeChild(body); 14405 parent.appendChild(body); 14406 14407 body.focus(); 14408 } 14409 }, 14410 14411 _isHidden : function() { 14412 var s; 14413 14414 if (!isGecko) 14415 return 0; 14416 14417 // Weird, wheres that cursor selection? 14418 s = this.selection.getSel(); 14419 return (!s || !s.rangeCount || s.rangeCount === 0); 14420 } 14421 }); 14422 })(tinymce); 14423 (function(tinymce) { 14424 var each = tinymce.each; 14425 14426 tinymce.Editor.prototype.setupEvents = function() { 14427 var self = this, settings = self.settings; 14428 14429 // Add events to the editor 14430 each([ 14431 'onPreInit', 14432 14433 'onBeforeRenderUI', 14434 14435 'onPostRender', 14436 14437 'onLoad', 14438 14439 'onInit', 14440 14441 'onRemove', 14442 14443 'onActivate', 14444 14445 'onDeactivate', 14446 14447 'onClick', 14448 14449 'onEvent', 14450 14451 'onMouseUp', 14452 14453 'onMouseDown', 14454 14455 'onDblClick', 14456 14457 'onKeyDown', 14458 14459 'onKeyUp', 14460 14461 'onKeyPress', 14462 14463 'onContextMenu', 14464 14465 'onSubmit', 14466 14467 'onReset', 14468 14469 'onPaste', 14470 14471 'onPreProcess', 14472 14473 'onPostProcess', 14474 14475 'onBeforeSetContent', 14476 14477 'onBeforeGetContent', 14478 14479 'onSetContent', 14480 14481 'onGetContent', 14482 14483 'onLoadContent', 14484 14485 'onSaveContent', 14486 14487 'onNodeChange', 14488 14489 'onChange', 14490 14491 'onBeforeExecCommand', 14492 14493 'onExecCommand', 14494 14495 'onUndo', 14496 14497 'onRedo', 14498 14499 'onVisualAid', 14500 14501 'onSetProgressState', 14502 14503 'onSetAttrib' 14504 ], function(name) { 14505 self[name] = new tinymce.util.Dispatcher(self); 14506 }); 14507 14508 // Handle legacy cleanup_callback option 14509 if (settings.cleanup_callback) { 14510 self.onBeforeSetContent.add(function(ed, o) { 14511 o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); 14512 }); 14513 14514 self.onPreProcess.add(function(ed, o) { 14515 if (o.set) 14516 ed.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o); 14517 14518 if (o.get) 14519 ed.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o); 14520 }); 14521 14522 self.onPostProcess.add(function(ed, o) { 14523 if (o.set) 14524 o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); 14525 14526 if (o.get) 14527 o.content = ed.execCallback('cleanup_callback', 'get_from_editor', o.content, o); 14528 }); 14529 } 14530 14531 // Handle legacy save_callback option 14532 if (settings.save_callback) { 14533 self.onGetContent.add(function(ed, o) { 14534 if (o.save) 14535 o.content = ed.execCallback('save_callback', ed.id, o.content, ed.getBody()); 14536 }); 14537 } 14538 14539 // Handle legacy handle_event_callback option 14540 if (settings.handle_event_callback) { 14541 self.onEvent.add(function(ed, e, o) { 14542 if (self.execCallback('handle_event_callback', e, ed, o) === false) { 14543 e.preventDefault(); 14544 e.stopPropagation(); 14545 } 14546 }); 14547 } 14548 14549 // Handle legacy handle_node_change_callback option 14550 if (settings.handle_node_change_callback) { 14551 self.onNodeChange.add(function(ed, cm, n) { 14552 ed.execCallback('handle_node_change_callback', ed.id, n, -1, -1, true, ed.selection.isCollapsed()); 14553 }); 14554 } 14555 14556 // Handle legacy save_callback option 14557 if (settings.save_callback) { 14558 self.onSaveContent.add(function(ed, o) { 14559 var h = ed.execCallback('save_callback', ed.id, o.content, ed.getBody()); 14560 14561 if (h) 14562 o.content = h; 14563 }); 14564 } 14565 14566 // Handle legacy onchange_callback option 14567 if (settings.onchange_callback) { 14568 self.onChange.add(function(ed, l) { 14569 ed.execCallback('onchange_callback', ed, l); 14570 }); 14571 } 14572 }; 14573 14574 tinymce.Editor.prototype.bindNativeEvents = function() { 14575 // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset 14576 var self = this, i, settings = self.settings, dom = self.dom, nativeToDispatcherMap; 14577 14578 nativeToDispatcherMap = { 14579 mouseup : 'onMouseUp', 14580 mousedown : 'onMouseDown', 14581 click : 'onClick', 14582 keyup : 'onKeyUp', 14583 keydown : 'onKeyDown', 14584 keypress : 'onKeyPress', 14585 submit : 'onSubmit', 14586 reset : 'onReset', 14587 contextmenu : 'onContextMenu', 14588 dblclick : 'onDblClick', 14589 paste : 'onPaste' // Doesn't work in all browsers yet 14590 }; 14591 14592 // Handler that takes a native event and sends it out to a dispatcher like onKeyDown 14593 function eventHandler(evt, args) { 14594 var type = evt.type; 14595 14596 // Don't fire events when it's removed 14597 if (self.removed) 14598 return; 14599 14600 // Sends the native event out to a global dispatcher then to the specific event dispatcher 14601 if (self.onEvent.dispatch(self, evt, args) !== false) { 14602 self[nativeToDispatcherMap[evt.fakeType || evt.type]].dispatch(self, evt, args); 14603 } 14604 }; 14605 14606 // Opera doesn't support focus event for contentEditable elements so we need to fake it 14607 function doOperaFocus(e) { 14608 self.focus(true); 14609 }; 14610 14611 function nodeChanged(ed, e) { 14612 // Normalize selection for example <b>a</b><i>|a</i> becomes <b>a|</b><i>a</i> except for Ctrl+A since it selects everything 14613 if (e.keyCode != 65 || !tinymce.VK.metaKeyPressed(e)) { 14614 self.selection.normalize(); 14615 } 14616 14617 self.nodeChanged(); 14618 } 14619 14620 // Add DOM events 14621 each(nativeToDispatcherMap, function(dispatcherName, nativeName) { 14622 var root = settings.content_editable ? self.getBody() : self.getDoc(); 14623 14624 switch (nativeName) { 14625 case 'contextmenu': 14626 dom.bind(root, nativeName, eventHandler); 14627 break; 14628 14629 case 'paste': 14630 dom.bind(self.getBody(), nativeName, eventHandler); 14631 break; 14632 14633 case 'submit': 14634 case 'reset': 14635 dom.bind(self.getElement().form || tinymce.DOM.getParent(self.id, 'form'), nativeName, eventHandler); 14636 break; 14637 14638 default: 14639 dom.bind(root, nativeName, eventHandler); 14640 } 14641 }); 14642 14643 // Set the editor as active when focused 14644 dom.bind(settings.content_editable ? self.getBody() : (tinymce.isGecko ? self.getDoc() : self.getWin()), 'focus', function(e) { 14645 self.focus(true); 14646 }); 14647 14648 if (settings.content_editable && tinymce.isOpera) { 14649 dom.bind(self.getBody(), 'click', doOperaFocus); 14650 dom.bind(self.getBody(), 'keydown', doOperaFocus); 14651 } 14652 14653 // Add node change handler 14654 self.onMouseUp.add(nodeChanged); 14655 14656 self.onKeyUp.add(function(ed, e) { 14657 var keyCode = e.keyCode; 14658 14659 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || keyCode == 46 || keyCode == 8 || (tinymce.isMac && (keyCode == 91 || keyCode == 93)) || e.ctrlKey) 14660 nodeChanged(ed, e); 14661 }); 14662 14663 // Add reset handler 14664 self.onReset.add(function() { 14665 self.setContent(self.startContent, {format : 'raw'}); 14666 }); 14667 14668 // Add shortcuts 14669 function handleShortcut(e, execute) { 14670 if (e.altKey || e.ctrlKey || e.metaKey) { 14671 each(self.shortcuts, function(shortcut) { 14672 var ctrlState = tinymce.isMac ? e.metaKey : e.ctrlKey; 14673 14674 if (shortcut.ctrl != ctrlState || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey) 14675 return; 14676 14677 if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) { 14678 e.preventDefault(); 14679 14680 if (execute) { 14681 shortcut.func.call(shortcut.scope); 14682 } 14683 14684 return true; 14685 } 14686 }); 14687 } 14688 }; 14689 14690 self.onKeyUp.add(function(ed, e) { 14691 handleShortcut(e); 14692 }); 14693 14694 self.onKeyPress.add(function(ed, e) { 14695 handleShortcut(e); 14696 }); 14697 14698 self.onKeyDown.add(function(ed, e) { 14699 handleShortcut(e, true); 14700 }); 14701 14702 if (tinymce.isOpera) { 14703 self.onClick.add(function(ed, e) { 14704 e.preventDefault(); 14705 }); 14706 } 14707 }; 14708 })(tinymce); 14709 (function(tinymce) { 14710 // Added for compression purposes 14711 var each = tinymce.each, undef, TRUE = true, FALSE = false; 14712 14713 tinymce.EditorCommands = function(editor) { 14714 var dom = editor.dom, 14715 selection = editor.selection, 14716 commands = {state: {}, exec : {}, value : {}}, 14717 settings = editor.settings, 14718 formatter = editor.formatter, 14719 bookmark; 14720 14721 function execCommand(command, ui, value) { 14722 var func; 14723 14724 command = command.toLowerCase(); 14725 if (func = commands.exec[command]) { 14726 func(command, ui, value); 14727 return TRUE; 14728 } 14729 14730 return FALSE; 14731 }; 14732 14733 function queryCommandState(command) { 14734 var func; 14735 14736 command = command.toLowerCase(); 14737 if (func = commands.state[command]) 14738 return func(command); 14739 14740 return -1; 14741 }; 14742 14743 function queryCommandValue(command) { 14744 var func; 14745 14746 command = command.toLowerCase(); 14747 if (func = commands.value[command]) 14748 return func(command); 14749 14750 return FALSE; 14751 }; 14752 14753 function addCommands(command_list, type) { 14754 type = type || 'exec'; 14755 14756 each(command_list, function(callback, command) { 14757 each(command.toLowerCase().split(','), function(command) { 14758 commands[type][command] = callback; 14759 }); 14760 }); 14761 }; 14762 14763 // Expose public methods 14764 tinymce.extend(this, { 14765 execCommand : execCommand, 14766 queryCommandState : queryCommandState, 14767 queryCommandValue : queryCommandValue, 14768 addCommands : addCommands 14769 }); 14770 14771 // Private methods 14772 14773 function execNativeCommand(command, ui, value) { 14774 if (ui === undef) 14775 ui = FALSE; 14776 14777 if (value === undef) 14778 value = null; 14779 14780 return editor.getDoc().execCommand(command, ui, value); 14781 }; 14782 14783 function isFormatMatch(name) { 14784 return formatter.match(name); 14785 }; 14786 14787 function toggleFormat(name, value) { 14788 formatter.toggle(name, value ? {value : value} : undef); 14789 }; 14790 14791 function storeSelection(type) { 14792 bookmark = selection.getBookmark(type); 14793 }; 14794 14795 function restoreSelection() { 14796 selection.moveToBookmark(bookmark); 14797 }; 14798 14799 // Add execCommand overrides 14800 addCommands({ 14801 // Ignore these, added for compatibility 14802 'mceResetDesignMode,mceBeginUndoLevel' : function() {}, 14803 14804 // Add undo manager logic 14805 'mceEndUndoLevel,mceAddUndoLevel' : function() { 14806 editor.undoManager.add(); 14807 }, 14808 14809 'Cut,Copy,Paste' : function(command) { 14810 var doc = editor.getDoc(), failed; 14811 14812 // Try executing the native command 14813 try { 14814 execNativeCommand(command); 14815 } catch (ex) { 14816 // Command failed 14817 failed = TRUE; 14818 } 14819 14820 // Present alert message about clipboard access not being available 14821 if (failed || !doc.queryCommandSupported(command)) { 14822 if (tinymce.isGecko) { 14823 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) { 14824 if (state) 14825 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank'); 14826 }); 14827 } else 14828 editor.windowManager.alert(editor.getLang('clipboard_no_support')); 14829 } 14830 }, 14831 14832 // Override unlink command 14833 unlink : function(command) { 14834 if (selection.isCollapsed()) 14835 selection.select(selection.getNode()); 14836 14837 execNativeCommand(command); 14838 selection.collapse(FALSE); 14839 }, 14840 14841 // Override justify commands to use the text formatter engine 14842 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { 14843 var align = command.substring(7); 14844 14845 // Remove all other alignments first 14846 each('left,center,right,full'.split(','), function(name) { 14847 if (align != name) 14848 formatter.remove('align' + name); 14849 }); 14850 14851 toggleFormat('align' + align); 14852 execCommand('mceRepaint'); 14853 }, 14854 14855 // Override list commands to fix WebKit bug 14856 'InsertUnorderedList,InsertOrderedList' : function(command) { 14857 var listElm, listParent; 14858 14859 execNativeCommand(command); 14860 14861 // WebKit produces lists within block elements so we need to split them 14862 // we will replace the native list creation logic to custom logic later on 14863 // TODO: Remove this when the list creation logic is removed 14864 listElm = dom.getParent(selection.getNode(), 'ol,ul'); 14865 if (listElm) { 14866 listParent = listElm.parentNode; 14867 14868 // If list is within a text block then split that block 14869 if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) { 14870 storeSelection(); 14871 dom.split(listParent, listElm); 14872 restoreSelection(); 14873 } 14874 } 14875 }, 14876 14877 // Override commands to use the text formatter engine 14878 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) { 14879 toggleFormat(command); 14880 }, 14881 14882 // Override commands to use the text formatter engine 14883 'ForeColor,HiliteColor,FontName' : function(command, ui, value) { 14884 toggleFormat(command, value); 14885 }, 14886 14887 FontSize : function(command, ui, value) { 14888 var fontClasses, fontSizes; 14889 14890 // Convert font size 1-7 to styles 14891 if (value >= 1 && value <= 7) { 14892 fontSizes = tinymce.explode(settings.font_size_style_values); 14893 fontClasses = tinymce.explode(settings.font_size_classes); 14894 14895 if (fontClasses) 14896 value = fontClasses[value - 1] || value; 14897 else 14898 value = fontSizes[value - 1] || value; 14899 } 14900 14901 toggleFormat(command, value); 14902 }, 14903 14904 RemoveFormat : function(command) { 14905 formatter.remove(command); 14906 }, 14907 14908 mceBlockQuote : function(command) { 14909 toggleFormat('blockquote'); 14910 }, 14911 14912 FormatBlock : function(command, ui, value) { 14913 return toggleFormat(value || 'p'); 14914 }, 14915 14916 mceCleanup : function() { 14917 var bookmark = selection.getBookmark(); 14918 14919 editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE}); 14920 14921 selection.moveToBookmark(bookmark); 14922 }, 14923 14924 mceRemoveNode : function(command, ui, value) { 14925 var node = value || selection.getNode(); 14926 14927 // Make sure that the body node isn't removed 14928 if (node != editor.getBody()) { 14929 storeSelection(); 14930 editor.dom.remove(node, TRUE); 14931 restoreSelection(); 14932 } 14933 }, 14934 14935 mceSelectNodeDepth : function(command, ui, value) { 14936 var counter = 0; 14937 14938 dom.getParent(selection.getNode(), function(node) { 14939 if (node.nodeType == 1 && counter++ == value) { 14940 selection.select(node); 14941 return FALSE; 14942 } 14943 }, editor.getBody()); 14944 }, 14945 14946 mceSelectNode : function(command, ui, value) { 14947 selection.select(value); 14948 }, 14949 14950 mceInsertContent : function(command, ui, value) { 14951 var parser, serializer, parentNode, rootNode, fragment, args, 14952 marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement; 14953 14954 //selection.normalize(); 14955 14956 // Setup parser and serializer 14957 parser = editor.parser; 14958 serializer = new tinymce.html.Serializer({}, editor.schema); 14959 bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>'; 14960 14961 // Run beforeSetContent handlers on the HTML to be inserted 14962 args = {content: value, format: 'html'}; 14963 selection.onBeforeSetContent.dispatch(selection, args); 14964 value = args.content; 14965 14966 // Add caret at end of contents if it's missing 14967 if (value.indexOf('{$caret}') == -1) 14968 value += '{$caret}'; 14969 14970 // Replace the caret marker with a span bookmark element 14971 value = value.replace(/\{\$caret\}/, bookmarkHtml); 14972 14973 // Insert node maker where we will insert the new HTML and get it's parent 14974 if (!selection.isCollapsed()) 14975 editor.getDoc().execCommand('Delete', false, null); 14976 14977 parentNode = selection.getNode(); 14978 14979 // Parse the fragment within the context of the parent node 14980 args = {context : parentNode.nodeName.toLowerCase()}; 14981 fragment = parser.parse(value, args); 14982 14983 // Move the caret to a more suitable location 14984 node = fragment.lastChild; 14985 if (node.attr('id') == 'mce_marker') { 14986 marker = node; 14987 14988 for (node = node.prev; node; node = node.walk(true)) { 14989 if (node.type == 3 || !dom.isBlock(node.name)) { 14990 node.parent.insert(marker, node, node.name === 'br'); 14991 break; 14992 } 14993 } 14994 } 14995 14996 // If parser says valid we can insert the contents into that parent 14997 if (!args.invalid) { 14998 value = serializer.serialize(fragment); 14999 15000 // Check if parent is empty or only has one BR element then set the innerHTML of that parent 15001 node = parentNode.firstChild; 15002 node2 = parentNode.lastChild; 15003 if (!node || (node === node2 && node.nodeName === 'BR')) 15004 dom.setHTML(parentNode, value); 15005 else 15006 selection.setContent(value); 15007 } else { 15008 // If the fragment was invalid within that context then we need 15009 // to parse and process the parent it's inserted into 15010 15011 // Insert bookmark node and get the parent 15012 selection.setContent(bookmarkHtml); 15013 parentNode = editor.selection.getNode(); 15014 rootNode = editor.getBody(); 15015 15016 // Opera will return the document node when selection is in root 15017 if (parentNode.nodeType == 9) 15018 parentNode = node = rootNode; 15019 else 15020 node = parentNode; 15021 15022 // Find the ancestor just before the root element 15023 while (node !== rootNode) { 15024 parentNode = node; 15025 node = node.parentNode; 15026 } 15027 15028 // Get the outer/inner HTML depending on if we are in the root and parser and serialize that 15029 value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode); 15030 value = serializer.serialize( 15031 parser.parse( 15032 // Need to replace by using a function since $ in the contents would otherwise be a problem 15033 value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() { 15034 return serializer.serialize(fragment); 15035 }) 15036 ) 15037 ); 15038 15039 // Set the inner/outer HTML depending on if we are in the root or not 15040 if (parentNode == rootNode) 15041 dom.setHTML(rootNode, value); 15042 else 15043 dom.setOuterHTML(parentNode, value); 15044 } 15045 15046 marker = dom.get('mce_marker'); 15047 15048 // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well 15049 nodeRect = dom.getRect(marker); 15050 viewPortRect = dom.getViewPort(editor.getWin()); 15051 15052 // Check if node is out side the viewport if it is then scroll to it 15053 if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) || 15054 (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) { 15055 viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody(); 15056 viewportBodyElement.scrollLeft = nodeRect.x; 15057 viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25; 15058 } 15059 15060 // Move selection before marker and remove it 15061 rng = dom.createRng(); 15062 15063 // If previous sibling is a text node set the selection to the end of that node 15064 node = marker.previousSibling; 15065 if (node && node.nodeType == 3) { 15066 rng.setStart(node, node.nodeValue.length); 15067 } else { 15068 // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node 15069 rng.setStartBefore(marker); 15070 rng.setEndBefore(marker); 15071 } 15072 15073 // Remove the marker node and set the new range 15074 dom.remove(marker); 15075 selection.setRng(rng); 15076 15077 // Dispatch after event and add any visual elements needed 15078 selection.onSetContent.dispatch(selection, args); 15079 editor.addVisual(); 15080 }, 15081 15082 mceInsertRawHTML : function(command, ui, value) { 15083 selection.setContent('tiny_mce_marker'); 15084 editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value })); 15085 }, 15086 15087 mceToggleFormat : function(command, ui, value) { 15088 toggleFormat(value); 15089 }, 15090 15091 mceSetContent : function(command, ui, value) { 15092 editor.setContent(value); 15093 }, 15094 15095 'Indent,Outdent' : function(command) { 15096 var intentValue, indentUnit, value; 15097 15098 // Setup indent level 15099 intentValue = settings.indentation; 15100 indentUnit = /[a-z%]+$/i.exec(intentValue); 15101 intentValue = parseInt(intentValue); 15102 15103 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) { 15104 // If forced_root_blocks is set to false we don't have a block to indent so lets create a div 15105 if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) { 15106 formatter.apply('div'); 15107 } 15108 15109 each(selection.getSelectedBlocks(), function(element) { 15110 if (command == 'outdent') { 15111 value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue); 15112 dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : ''); 15113 } else 15114 dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit); 15115 }); 15116 } else 15117 execNativeCommand(command); 15118 }, 15119 15120 mceRepaint : function() { 15121 var bookmark; 15122 15123 if (tinymce.isGecko) { 15124 try { 15125 storeSelection(TRUE); 15126 15127 if (selection.getSel()) 15128 selection.getSel().selectAllChildren(editor.getBody()); 15129 15130 selection.collapse(TRUE); 15131 restoreSelection(); 15132 } catch (ex) { 15133 // Ignore 15134 } 15135 } 15136 }, 15137 15138 mceToggleFormat : function(command, ui, value) { 15139 formatter.toggle(value); 15140 }, 15141 15142 InsertHorizontalRule : function() { 15143 editor.execCommand('mceInsertContent', false, '<hr />'); 15144 }, 15145 15146 mceToggleVisualAid : function() { 15147 editor.hasVisual = !editor.hasVisual; 15148 editor.addVisual(); 15149 }, 15150 15151 mceReplaceContent : function(command, ui, value) { 15152 editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'}))); 15153 }, 15154 15155 mceInsertLink : function(command, ui, value) { 15156 var anchor; 15157 15158 if (typeof(value) == 'string') 15159 value = {href : value}; 15160 15161 anchor = dom.getParent(selection.getNode(), 'a'); 15162 15163 // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here. 15164 value.href = value.href.replace(' ', '%20'); 15165 15166 // Remove existing links if there could be child links or that the href isn't specified 15167 if (!anchor || !value.href) { 15168 formatter.remove('link'); 15169 } 15170 15171 // Apply new link to selection 15172 if (value.href) { 15173 formatter.apply('link', value, anchor); 15174 } 15175 }, 15176 15177 selectAll : function() { 15178 var root = dom.getRoot(), rng = dom.createRng(); 15179 15180 rng.setStart(root, 0); 15181 rng.setEnd(root, root.childNodes.length); 15182 15183 editor.selection.setRng(rng); 15184 } 15185 }); 15186 15187 // Add queryCommandState overrides 15188 addCommands({ 15189 // Override justify commands 15190 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { 15191 var name = 'align' + command.substring(7); 15192 var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks(); 15193 var matches = tinymce.map(nodes, function(node) { 15194 return !!formatter.matchNode(node, name); 15195 }); 15196 return tinymce.inArray(matches, TRUE) !== -1; 15197 }, 15198 15199 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) { 15200 return isFormatMatch(command); 15201 }, 15202 15203 mceBlockQuote : function() { 15204 return isFormatMatch('blockquote'); 15205 }, 15206 15207 Outdent : function() { 15208 var node; 15209 15210 if (settings.inline_styles) { 15211 if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) 15212 return TRUE; 15213 15214 if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) 15215 return TRUE; 15216 } 15217 15218 return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE')); 15219 }, 15220 15221 'InsertUnorderedList,InsertOrderedList' : function(command) { 15222 return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL'); 15223 } 15224 }, 'state'); 15225 15226 // Add queryCommandValue overrides 15227 addCommands({ 15228 'FontSize,FontName' : function(command) { 15229 var value = 0, parent; 15230 15231 if (parent = dom.getParent(selection.getNode(), 'span')) { 15232 if (command == 'fontsize') 15233 value = parent.style.fontSize; 15234 else 15235 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase(); 15236 } 15237 15238 return value; 15239 } 15240 }, 'value'); 15241 15242 // Add undo manager logic 15243 addCommands({ 15244 Undo : function() { 15245 editor.undoManager.undo(); 15246 }, 15247 15248 Redo : function() { 15249 editor.undoManager.redo(); 15250 } 15251 }); 15252 }; 15253 })(tinymce); 15254 15255 (function(tinymce) { 15256 var Dispatcher = tinymce.util.Dispatcher; 15257 15258 tinymce.UndoManager = function(editor) { 15259 var self, index = 0, data = [], beforeBookmark, onAdd, onUndo, onRedo; 15260 15261 function getContent() { 15262 // Remove whitespace before/after and remove pure bogus nodes 15263 return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}).replace(/<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>/g, '')); 15264 }; 15265 15266 function addNonTypingUndoLevel() { 15267 self.typing = false; 15268 self.add(); 15269 }; 15270 15271 // Create event instances 15272 onBeforeAdd = new Dispatcher(self); 15273 onAdd = new Dispatcher(self); 15274 onUndo = new Dispatcher(self); 15275 onRedo = new Dispatcher(self); 15276 15277 // Pass though onAdd event from UndoManager to Editor as onChange 15278 onAdd.add(function(undoman, level) { 15279 if (undoman.hasUndo()) 15280 return editor.onChange.dispatch(editor, level, undoman); 15281 }); 15282 15283 // Pass though onUndo event from UndoManager to Editor 15284 onUndo.add(function(undoman, level) { 15285 return editor.onUndo.dispatch(editor, level, undoman); 15286 }); 15287 15288 // Pass though onRedo event from UndoManager to Editor 15289 onRedo.add(function(undoman, level) { 15290 return editor.onRedo.dispatch(editor, level, undoman); 15291 }); 15292 15293 // Add initial undo level when the editor is initialized 15294 editor.onInit.add(function() { 15295 self.add(); 15296 }); 15297 15298 // Get position before an execCommand is processed 15299 editor.onBeforeExecCommand.add(function(ed, cmd, ui, val, args) { 15300 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) { 15301 self.beforeChange(); 15302 } 15303 }); 15304 15305 // Add undo level after an execCommand call was made 15306 editor.onExecCommand.add(function(ed, cmd, ui, val, args) { 15307 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) { 15308 self.add(); 15309 } 15310 }); 15311 15312 // Add undo level on save contents, drag end and blur/focusout 15313 editor.onSaveContent.add(addNonTypingUndoLevel); 15314 editor.dom.bind(editor.dom.getRoot(), 'dragend', addNonTypingUndoLevel); 15315 editor.dom.bind(editor.getDoc(), tinymce.isGecko ? 'blur' : 'focusout', function(e) { 15316 if (!editor.removed && self.typing) { 15317 addNonTypingUndoLevel(); 15318 } 15319 }); 15320 15321 editor.onKeyUp.add(function(editor, e) { 15322 var keyCode = e.keyCode; 15323 15324 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) { 15325 addNonTypingUndoLevel(); 15326 } 15327 }); 15328 15329 editor.onKeyDown.add(function(editor, e) { 15330 var keyCode = e.keyCode; 15331 15332 // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter 15333 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) { 15334 if (self.typing) { 15335 addNonTypingUndoLevel(); 15336 } 15337 15338 return; 15339 } 15340 15341 // If key isn't shift,ctrl,alt,capslock,metakey 15342 if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing) { 15343 self.beforeChange(); 15344 self.typing = true; 15345 self.add(); 15346 } 15347 }); 15348 15349 editor.onMouseDown.add(function(editor, e) { 15350 if (self.typing) { 15351 addNonTypingUndoLevel(); 15352 } 15353 }); 15354 15355 // Add keyboard shortcuts for undo/redo keys 15356 editor.addShortcut('ctrl+z', 'undo_desc', 'Undo'); 15357 editor.addShortcut('ctrl+y', 'redo_desc', 'Redo'); 15358 15359 self = { 15360 // Explose for debugging reasons 15361 data : data, 15362 15363 typing : false, 15364 15365 onBeforeAdd: onBeforeAdd, 15366 15367 onAdd : onAdd, 15368 15369 onUndo : onUndo, 15370 15371 onRedo : onRedo, 15372 15373 beforeChange : function() { 15374 beforeBookmark = editor.selection.getBookmark(2, true); 15375 }, 15376 15377 add : function(level) { 15378 var i, settings = editor.settings, lastLevel; 15379 15380 level = level || {}; 15381 level.content = getContent(); 15382 15383 self.onBeforeAdd.dispatch(self, level); 15384 15385 // Add undo level if needed 15386 lastLevel = data[index]; 15387 if (lastLevel && lastLevel.content == level.content) 15388 return null; 15389 15390 // Set before bookmark on previous level 15391 if (data[index]) 15392 data[index].beforeBookmark = beforeBookmark; 15393 15394 // Time to compress 15395 if (settings.custom_undo_redo_levels) { 15396 if (data.length > settings.custom_undo_redo_levels) { 15397 for (i = 0; i < data.length - 1; i++) 15398 data[i] = data[i + 1]; 15399 15400 data.length--; 15401 index = data.length; 15402 } 15403 } 15404 15405 // Get a non intrusive normalized bookmark 15406 level.bookmark = editor.selection.getBookmark(2, true); 15407 15408 // Crop array if needed 15409 if (index < data.length - 1) 15410 data.length = index + 1; 15411 15412 data.push(level); 15413 index = data.length - 1; 15414 15415 self.onAdd.dispatch(self, level); 15416 editor.isNotDirty = 0; 15417 15418 return level; 15419 }, 15420 15421 undo : function() { 15422 var level, i; 15423 15424 if (self.typing) { 15425 self.add(); 15426 self.typing = false; 15427 } 15428 15429 if (index > 0) { 15430 level = data[--index]; 15431 15432 editor.setContent(level.content, {format : 'raw'}); 15433 editor.selection.moveToBookmark(level.beforeBookmark); 15434 15435 self.onUndo.dispatch(self, level); 15436 } 15437 15438 return level; 15439 }, 15440 15441 redo : function() { 15442 var level; 15443 15444 if (index < data.length - 1) { 15445 level = data[++index]; 15446 15447 editor.setContent(level.content, {format : 'raw'}); 15448 editor.selection.moveToBookmark(level.bookmark); 15449 15450 self.onRedo.dispatch(self, level); 15451 } 15452 15453 return level; 15454 }, 15455 15456 clear : function() { 15457 data = []; 15458 index = 0; 15459 self.typing = false; 15460 }, 15461 15462 hasUndo : function() { 15463 return index > 0 || this.typing; 15464 }, 15465 15466 hasRedo : function() { 15467 return index < data.length - 1 && !this.typing; 15468 } 15469 }; 15470 15471 return self; 15472 }; 15473 })(tinymce); 15474 15475 tinymce.ForceBlocks = function(editor) { 15476 var settings = editor.settings, dom = editor.dom, selection = editor.selection, blockElements = editor.schema.getBlockElements(); 15477 15478 function addRootBlocks() { 15479 var node = selection.getStart(), rootNode = editor.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF, wrapped, isInEditorDocument; 15480 15481 if (!node || node.nodeType !== 1 || !settings.forced_root_block) 15482 return; 15483 15484 // Check if node is wrapped in block 15485 while (node && node != rootNode) { 15486 if (blockElements[node.nodeName]) 15487 return; 15488 15489 node = node.parentNode; 15490 } 15491 15492 // Get current selection 15493 rng = selection.getRng(); 15494 if (rng.setStart) { 15495 startContainer = rng.startContainer; 15496 startOffset = rng.startOffset; 15497 endContainer = rng.endContainer; 15498 endOffset = rng.endOffset; 15499 } else { 15500 // Force control range into text range 15501 if (rng.item) { 15502 node = rng.item(0); 15503 rng = editor.getDoc().body.createTextRange(); 15504 rng.moveToElementText(node); 15505 } 15506 15507 isInEditorDocument = rng.parentElement().ownerDocument === editor.getDoc(); 15508 tmpRng = rng.duplicate(); 15509 tmpRng.collapse(true); 15510 startOffset = tmpRng.move('character', offset) * -1; 15511 15512 if (!tmpRng.collapsed) { 15513 tmpRng = rng.duplicate(); 15514 tmpRng.collapse(false); 15515 endOffset = (tmpRng.move('character', offset) * -1) - startOffset; 15516 } 15517 } 15518 15519 // Wrap non block elements and text nodes 15520 node = rootNode.firstChild; 15521 while (node) { 15522 if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) { 15523 if (!rootBlockNode) { 15524 rootBlockNode = dom.create(settings.forced_root_block); 15525 node.parentNode.insertBefore(rootBlockNode, node); 15526 wrapped = true; 15527 } 15528 15529 tempNode = node; 15530 node = node.nextSibling; 15531 rootBlockNode.appendChild(tempNode); 15532 } else { 15533 rootBlockNode = null; 15534 node = node.nextSibling; 15535 } 15536 } 15537 15538 if (wrapped) { 15539 if (rng.setStart) { 15540 rng.setStart(startContainer, startOffset); 15541 rng.setEnd(endContainer, endOffset); 15542 selection.setRng(rng); 15543 } else { 15544 // Only select if the previous selection was inside the document to prevent auto focus in quirks mode 15545 if (isInEditorDocument) { 15546 try { 15547 rng = editor.getDoc().body.createTextRange(); 15548 rng.moveToElementText(rootNode); 15549 rng.collapse(true); 15550 rng.moveStart('character', startOffset); 15551 15552 if (endOffset > 0) 15553 rng.moveEnd('character', endOffset); 15554 15555 rng.select(); 15556 } catch (ex) { 15557 // Ignore 15558 } 15559 } 15560 } 15561 15562 editor.nodeChanged(); 15563 } 15564 }; 15565 15566 // Force root blocks 15567 if (settings.forced_root_block) { 15568 editor.onKeyUp.add(addRootBlocks); 15569 editor.onNodeChange.add(addRootBlocks); 15570 } 15571 }; 15572 15573 (function(tinymce) { 15574 // Shorten names 15575 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend; 15576 15577 tinymce.create('tinymce.ControlManager', { 15578 ControlManager : function(ed, s) { 15579 var t = this, i; 15580 15581 s = s || {}; 15582 t.editor = ed; 15583 t.controls = {}; 15584 t.onAdd = new tinymce.util.Dispatcher(t); 15585 t.onPostRender = new tinymce.util.Dispatcher(t); 15586 t.prefix = s.prefix || ed.id + '_'; 15587 t._cls = {}; 15588 15589 t.onPostRender.add(function() { 15590 each(t.controls, function(c) { 15591 c.postRender(); 15592 }); 15593 }); 15594 }, 15595 15596 get : function(id) { 15597 return this.controls[this.prefix + id] || this.controls[id]; 15598 }, 15599 15600 setActive : function(id, s) { 15601 var c = null; 15602 15603 if (c = this.get(id)) 15604 c.setActive(s); 15605 15606 return c; 15607 }, 15608 15609 setDisabled : function(id, s) { 15610 var c = null; 15611 15612 if (c = this.get(id)) 15613 c.setDisabled(s); 15614 15615 return c; 15616 }, 15617 15618 add : function(c) { 15619 var t = this; 15620 15621 if (c) { 15622 t.controls[c.id] = c; 15623 t.onAdd.dispatch(c, t); 15624 } 15625 15626 return c; 15627 }, 15628 15629 createControl : function(name) { 15630 var ctrl, i, l, self = this, editor = self.editor, factories, ctrlName; 15631 15632 // Build control factory cache 15633 if (!self.controlFactories) { 15634 self.controlFactories = []; 15635 each(editor.plugins, function(plugin) { 15636 if (plugin.createControl) { 15637 self.controlFactories.push(plugin); 15638 } 15639 }); 15640 } 15641 15642 // Create controls by asking cached factories 15643 factories = self.controlFactories; 15644 for (i = 0, l = factories.length; i < l; i++) { 15645 ctrl = factories[i].createControl(name, self); 15646 15647 if (ctrl) { 15648 return self.add(ctrl); 15649 } 15650 } 15651 15652 // Create sepearator 15653 if (name === "|" || name === "separator") { 15654 return self.createSeparator(); 15655 } 15656 15657 // Create control from button collection 15658 if (editor.buttons && (ctrl = editor.buttons[name])) { 15659 return self.createButton(name, ctrl); 15660 } 15661 15662 return self.add(ctrl); 15663 }, 15664 15665 createDropMenu : function(id, s, cc) { 15666 var t = this, ed = t.editor, c, bm, v, cls; 15667 15668 s = extend({ 15669 'class' : 'mceDropDown', 15670 constrain : ed.settings.constrain_menus 15671 }, s); 15672 15673 s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin'; 15674 if (v = ed.getParam('skin_variant')) 15675 s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1); 15676 15677 s['class'] += ed.settings.directionality == "rtl" ? ' mceRtl' : ''; 15678 15679 id = t.prefix + id; 15680 cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu; 15681 c = t.controls[id] = new cls(id, s); 15682 c.onAddItem.add(function(c, o) { 15683 var s = o.settings; 15684 15685 s.title = ed.getLang(s.title, s.title); 15686 15687 if (!s.onclick) { 15688 s.onclick = function(v) { 15689 if (s.cmd) 15690 ed.execCommand(s.cmd, s.ui || false, s.value); 15691 }; 15692 } 15693 }); 15694 15695 ed.onRemove.add(function() { 15696 c.destroy(); 15697 }); 15698 15699 // Fix for bug #1897785, #1898007 15700 if (tinymce.isIE) { 15701 c.onShowMenu.add(function() { 15702 // IE 8 needs focus in order to store away a range with the current collapsed caret location 15703 ed.focus(); 15704 15705 bm = ed.selection.getBookmark(1); 15706 }); 15707 15708 c.onHideMenu.add(function() { 15709 if (bm) { 15710 ed.selection.moveToBookmark(bm); 15711 bm = 0; 15712 } 15713 }); 15714 } 15715 15716 return t.add(c); 15717 }, 15718 15719 createListBox : function(id, s, cc) { 15720 var t = this, ed = t.editor, cmd, c, cls; 15721 15722 if (t.get(id)) 15723 return null; 15724 15725 s.title = ed.translate(s.title); 15726 s.scope = s.scope || ed; 15727 15728 if (!s.onselect) { 15729 s.onselect = function(v) { 15730 ed.execCommand(s.cmd, s.ui || false, v || s.value); 15731 }; 15732 } 15733 15734 s = extend({ 15735 title : s.title, 15736 'class' : 'mce_' + id, 15737 scope : s.scope, 15738 control_manager : t 15739 }, s); 15740 15741 id = t.prefix + id; 15742 15743 15744 function useNativeListForAccessibility(ed) { 15745 return ed.settings.use_accessible_selects && !tinymce.isGecko 15746 } 15747 15748 if (ed.settings.use_native_selects || useNativeListForAccessibility(ed)) 15749 c = new tinymce.ui.NativeListBox(id, s); 15750 else { 15751 cls = cc || t._cls.listbox || tinymce.ui.ListBox; 15752 c = new cls(id, s, ed); 15753 } 15754 15755 t.controls[id] = c; 15756 15757 // Fix focus problem in Safari 15758 if (tinymce.isWebKit) { 15759 c.onPostRender.add(function(c, n) { 15760 // Store bookmark on mousedown 15761 Event.add(n, 'mousedown', function() { 15762 ed.bookmark = ed.selection.getBookmark(1); 15763 }); 15764 15765 // Restore on focus, since it might be lost 15766 Event.add(n, 'focus', function() { 15767 ed.selection.moveToBookmark(ed.bookmark); 15768 ed.bookmark = null; 15769 }); 15770 }); 15771 } 15772 15773 if (c.hideMenu) 15774 ed.onMouseDown.add(c.hideMenu, c); 15775 15776 return t.add(c); 15777 }, 15778 15779 createButton : function(id, s, cc) { 15780 var t = this, ed = t.editor, o, c, cls; 15781 15782 if (t.get(id)) 15783 return null; 15784 15785 s.title = ed.translate(s.title); 15786 s.label = ed.translate(s.label); 15787 s.scope = s.scope || ed; 15788 15789 if (!s.onclick && !s.menu_button) { 15790 s.onclick = function() { 15791 ed.execCommand(s.cmd, s.ui || false, s.value); 15792 }; 15793 } 15794 15795 s = extend({ 15796 title : s.title, 15797 'class' : 'mce_' + id, 15798 unavailable_prefix : ed.getLang('unavailable', ''), 15799 scope : s.scope, 15800 control_manager : t 15801 }, s); 15802 15803 id = t.prefix + id; 15804 15805 if (s.menu_button) { 15806 cls = cc || t._cls.menubutton || tinymce.ui.MenuButton; 15807 c = new cls(id, s, ed); 15808 ed.onMouseDown.add(c.hideMenu, c); 15809 } else { 15810 cls = t._cls.button || tinymce.ui.Button; 15811 c = new cls(id, s, ed); 15812 } 15813 15814 return t.add(c); 15815 }, 15816 15817 createMenuButton : function(id, s, cc) { 15818 s = s || {}; 15819 s.menu_button = 1; 15820 15821 return this.createButton(id, s, cc); 15822 }, 15823 15824 createSplitButton : function(id, s, cc) { 15825 var t = this, ed = t.editor, cmd, c, cls; 15826 15827 if (t.get(id)) 15828 return null; 15829 15830 s.title = ed.translate(s.title); 15831 s.scope = s.scope || ed; 15832 15833 if (!s.onclick) { 15834 s.onclick = function(v) { 15835 ed.execCommand(s.cmd, s.ui || false, v || s.value); 15836 }; 15837 } 15838 15839 if (!s.onselect) { 15840 s.onselect = function(v) { 15841 ed.execCommand(s.cmd, s.ui || false, v || s.value); 15842 }; 15843 } 15844 15845 s = extend({ 15846 title : s.title, 15847 'class' : 'mce_' + id, 15848 scope : s.scope, 15849 control_manager : t 15850 }, s); 15851 15852 id = t.prefix + id; 15853 cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton; 15854 c = t.add(new cls(id, s, ed)); 15855 ed.onMouseDown.add(c.hideMenu, c); 15856 15857 return c; 15858 }, 15859 15860 createColorSplitButton : function(id, s, cc) { 15861 var t = this, ed = t.editor, cmd, c, cls, bm; 15862 15863 if (t.get(id)) 15864 return null; 15865 15866 s.title = ed.translate(s.title); 15867 s.scope = s.scope || ed; 15868 15869 if (!s.onclick) { 15870 s.onclick = function(v) { 15871 if (tinymce.isIE) 15872 bm = ed.selection.getBookmark(1); 15873 15874 ed.execCommand(s.cmd, s.ui || false, v || s.value); 15875 }; 15876 } 15877 15878 if (!s.onselect) { 15879 s.onselect = function(v) { 15880 ed.execCommand(s.cmd, s.ui || false, v || s.value); 15881 }; 15882 } 15883 15884 s = extend({ 15885 title : s.title, 15886 'class' : 'mce_' + id, 15887 'menu_class' : ed.getParam('skin') + 'Skin', 15888 scope : s.scope, 15889 more_colors_title : ed.getLang('more_colors') 15890 }, s); 15891 15892 id = t.prefix + id; 15893 cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton; 15894 c = new cls(id, s, ed); 15895 ed.onMouseDown.add(c.hideMenu, c); 15896 15897 // Remove the menu element when the editor is removed 15898 ed.onRemove.add(function() { 15899 c.destroy(); 15900 }); 15901 15902 // Fix for bug #1897785, #1898007 15903 if (tinymce.isIE) { 15904 c.onShowMenu.add(function() { 15905 // IE 8 needs focus in order to store away a range with the current collapsed caret location 15906 ed.focus(); 15907 bm = ed.selection.getBookmark(1); 15908 }); 15909 15910 c.onHideMenu.add(function() { 15911 if (bm) { 15912 ed.selection.moveToBookmark(bm); 15913 bm = 0; 15914 } 15915 }); 15916 } 15917 15918 return t.add(c); 15919 }, 15920 15921 createToolbar : function(id, s, cc) { 15922 var c, t = this, cls; 15923 15924 id = t.prefix + id; 15925 cls = cc || t._cls.toolbar || tinymce.ui.Toolbar; 15926 c = new cls(id, s, t.editor); 15927 15928 if (t.get(id)) 15929 return null; 15930 15931 return t.add(c); 15932 }, 15933 15934 createToolbarGroup : function(id, s, cc) { 15935 var c, t = this, cls; 15936 id = t.prefix + id; 15937 cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup; 15938 c = new cls(id, s, t.editor); 15939 15940 if (t.get(id)) 15941 return null; 15942 15943 return t.add(c); 15944 }, 15945 15946 createSeparator : function(cc) { 15947 var cls = cc || this._cls.separator || tinymce.ui.Separator; 15948 15949 return new cls(); 15950 }, 15951 15952 setControlType : function(n, c) { 15953 return this._cls[n.toLowerCase()] = c; 15954 }, 15955 15956 destroy : function() { 15957 each(this.controls, function(c) { 15958 c.destroy(); 15959 }); 15960 15961 this.controls = null; 15962 } 15963 }); 15964 })(tinymce); 15965 15966 (function(tinymce) { 15967 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera; 15968 15969 tinymce.create('tinymce.WindowManager', { 15970 WindowManager : function(ed) { 15971 var t = this; 15972 15973 t.editor = ed; 15974 t.onOpen = new Dispatcher(t); 15975 t.onClose = new Dispatcher(t); 15976 t.params = {}; 15977 t.features = {}; 15978 }, 15979 15980 open : function(s, p) { 15981 var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u; 15982 15983 // Default some options 15984 s = s || {}; 15985 p = p || {}; 15986 sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window 15987 sh = isOpera ? vp.h : screen.height; 15988 s.name = s.name || 'mc_' + new Date().getTime(); 15989 s.width = parseInt(s.width || 320); 15990 s.height = parseInt(s.height || 240); 15991 s.resizable = true; 15992 s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0); 15993 s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0); 15994 p.inline = false; 15995 p.mce_width = s.width; 15996 p.mce_height = s.height; 15997 p.mce_auto_focus = s.auto_focus; 15998 15999 if (mo) { 16000 if (isIE) { 16001 s.center = true; 16002 s.help = false; 16003 s.dialogWidth = s.width + 'px'; 16004 s.dialogHeight = s.height + 'px'; 16005 s.scroll = s.scrollbars || false; 16006 } 16007 } 16008 16009 // Build features string 16010 each(s, function(v, k) { 16011 if (tinymce.is(v, 'boolean')) 16012 v = v ? 'yes' : 'no'; 16013 16014 if (!/^(name|url)$/.test(k)) { 16015 if (isIE && mo) 16016 f += (f ? ';' : '') + k + ':' + v; 16017 else 16018 f += (f ? ',' : '') + k + '=' + v; 16019 } 16020 }); 16021 16022 t.features = s; 16023 t.params = p; 16024 t.onOpen.dispatch(t, s, p); 16025 16026 u = s.url || s.file; 16027 u = tinymce._addVer(u); 16028 16029 try { 16030 if (isIE && mo) { 16031 w = 1; 16032 window.showModalDialog(u, window, f); 16033 } else 16034 w = window.open(u, s.name, f); 16035 } catch (ex) { 16036 // Ignore 16037 } 16038 16039 if (!w) 16040 alert(t.editor.getLang('popup_blocked')); 16041 }, 16042 16043 close : function(w) { 16044 w.close(); 16045 this.onClose.dispatch(this); 16046 }, 16047 16048 createInstance : function(cl, a, b, c, d, e) { 16049 var f = tinymce.resolve(cl); 16050 16051 return new f(a, b, c, d, e); 16052 }, 16053 16054 confirm : function(t, cb, s, w) { 16055 w = w || window; 16056 16057 cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t)))); 16058 }, 16059 16060 alert : function(tx, cb, s, w) { 16061 var t = this; 16062 16063 w = w || window; 16064 w.alert(t._decode(t.editor.getLang(tx, tx))); 16065 16066 if (cb) 16067 cb.call(s || t); 16068 }, 16069 16070 resizeBy : function(dw, dh, win) { 16071 win.resizeBy(dw, dh); 16072 }, 16073 16074 // Internal functions 16075 16076 _decode : function(s) { 16077 return tinymce.DOM.decode(s).replace(/\\n/g, '\n'); 16078 } 16079 }); 16080 }(tinymce)); 16081 (function(tinymce) { 16082 tinymce.Formatter = function(ed) { 16083 var formats = {}, 16084 each = tinymce.each, 16085 dom = ed.dom, 16086 selection = ed.selection, 16087 TreeWalker = tinymce.dom.TreeWalker, 16088 rangeUtils = new tinymce.dom.RangeUtils(dom), 16089 isValid = ed.schema.isValidChild, 16090 isBlock = dom.isBlock, 16091 forcedRootBlock = ed.settings.forced_root_block, 16092 nodeIndex = dom.nodeIndex, 16093 INVISIBLE_CHAR = tinymce.isGecko ? '\u200B' : '\uFEFF', 16094 MCE_ATTR_RE = /^(src|href|style)$/, 16095 FALSE = false, 16096 TRUE = true, 16097 formatChangeData, 16098 undef, 16099 getContentEditable = dom.getContentEditable; 16100 16101 function isArray(obj) { 16102 return obj instanceof Array; 16103 }; 16104 16105 function getParents(node, selector) { 16106 return dom.getParents(node, selector, dom.getRoot()); 16107 }; 16108 16109 function isCaretNode(node) { 16110 return node.nodeType === 1 && node.id === '_mce_caret'; 16111 }; 16112 16113 function defaultFormats() { 16114 register({ 16115 alignleft : [ 16116 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}, defaultBlock: 'div'}, 16117 {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}} 16118 ], 16119 16120 aligncenter : [ 16121 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}, defaultBlock: 'div'}, 16122 {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}}, 16123 {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}} 16124 ], 16125 16126 alignright : [ 16127 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}, defaultBlock: 'div'}, 16128 {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}} 16129 ], 16130 16131 alignfull : [ 16132 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}, defaultBlock: 'div'} 16133 ], 16134 16135 bold : [ 16136 {inline : 'strong', remove : 'all'}, 16137 {inline : 'span', styles : {fontWeight : 'bold'}}, 16138 {inline : 'b', remove : 'all'} 16139 ], 16140 16141 italic : [ 16142 {inline : 'em', remove : 'all'}, 16143 {inline : 'span', styles : {fontStyle : 'italic'}}, 16144 {inline : 'i', remove : 'all'} 16145 ], 16146 16147 underline : [ 16148 {inline : 'span', styles : {textDecoration : 'underline'}, exact : true}, 16149 {inline : 'u', remove : 'all'} 16150 ], 16151 16152 strikethrough : [ 16153 {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true}, 16154 {inline : 'strike', remove : 'all'} 16155 ], 16156 16157 forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false}, 16158 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false}, 16159 fontname : {inline : 'span', styles : {fontFamily : '%value'}}, 16160 fontsize : {inline : 'span', styles : {fontSize : '%value'}}, 16161 fontsize_class : {inline : 'span', attributes : {'class' : '%value'}}, 16162 blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'}, 16163 subscript : {inline : 'sub'}, 16164 superscript : {inline : 'sup'}, 16165 16166 link : {inline : 'a', selector : 'a', remove : 'all', split : true, deep : true, 16167 onmatch : function(node) { 16168 return true; 16169 }, 16170 16171 onformat : function(elm, fmt, vars) { 16172 each(vars, function(value, key) { 16173 dom.setAttrib(elm, key, value); 16174 }); 16175 } 16176 }, 16177 16178 removeformat : [ 16179 {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true}, 16180 {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true}, 16181 {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true} 16182 ] 16183 }); 16184 16185 // Register default block formats 16186 each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) { 16187 register(name, {block : name, remove : 'all'}); 16188 }); 16189 16190 // Register user defined formats 16191 register(ed.settings.formats); 16192 }; 16193 16194 function addKeyboardShortcuts() { 16195 // Add some inline shortcuts 16196 ed.addShortcut('ctrl+b', 'bold_desc', 'Bold'); 16197 ed.addShortcut('ctrl+i', 'italic_desc', 'Italic'); 16198 ed.addShortcut('ctrl+u', 'underline_desc', 'Underline'); 16199 16200 // BlockFormat shortcuts keys 16201 for (var i = 1; i <= 6; i++) { 16202 ed.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]); 16203 } 16204 16205 ed.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']); 16206 ed.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']); 16207 ed.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']); 16208 }; 16209 16210 // Public functions 16211 16212 function get(name) { 16213 return name ? formats[name] : formats; 16214 }; 16215 16216 function register(name, format) { 16217 if (name) { 16218 if (typeof(name) !== 'string') { 16219 each(name, function(format, name) { 16220 register(name, format); 16221 }); 16222 } else { 16223 // Force format into array and add it to internal collection 16224 format = format.length ? format : [format]; 16225 16226 each(format, function(format) { 16227 // Set deep to false by default on selector formats this to avoid removing 16228 // alignment on images inside paragraphs when alignment is changed on paragraphs 16229 if (format.deep === undef) 16230 format.deep = !format.selector; 16231 16232 // Default to true 16233 if (format.split === undef) 16234 format.split = !format.selector || format.inline; 16235 16236 // Default to true 16237 if (format.remove === undef && format.selector && !format.inline) 16238 format.remove = 'none'; 16239 16240 // Mark format as a mixed format inline + block level 16241 if (format.selector && format.inline) { 16242 format.mixed = true; 16243 format.block_expand = true; 16244 } 16245 16246 // Split classes if needed 16247 if (typeof(format.classes) === 'string') 16248 format.classes = format.classes.split(/\s+/); 16249 }); 16250 16251 formats[name] = format; 16252 } 16253 } 16254 }; 16255 16256 var getTextDecoration = function(node) { 16257 var decoration; 16258 16259 ed.dom.getParent(node, function(n) { 16260 decoration = ed.dom.getStyle(n, 'text-decoration'); 16261 return decoration && decoration !== 'none'; 16262 }); 16263 16264 return decoration; 16265 }; 16266 16267 var processUnderlineAndColor = function(node) { 16268 var textDecoration; 16269 if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) { 16270 textDecoration = getTextDecoration(node.parentNode); 16271 if (ed.dom.getStyle(node, 'color') && textDecoration) { 16272 ed.dom.setStyle(node, 'text-decoration', textDecoration); 16273 } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) { 16274 ed.dom.setStyle(node, 'text-decoration', null); 16275 } 16276 } 16277 }; 16278 16279 function apply(name, vars, node) { 16280 var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed(); 16281 16282 function setElementFormat(elm, fmt) { 16283 fmt = fmt || format; 16284 16285 if (elm) { 16286 if (fmt.onformat) { 16287 fmt.onformat(elm, fmt, vars, node); 16288 } 16289 16290 each(fmt.styles, function(value, name) { 16291 dom.setStyle(elm, name, replaceVars(value, vars)); 16292 }); 16293 16294 each(fmt.attributes, function(value, name) { 16295 dom.setAttrib(elm, name, replaceVars(value, vars)); 16296 }); 16297 16298 each(fmt.classes, function(value) { 16299 value = replaceVars(value, vars); 16300 16301 if (!dom.hasClass(elm, value)) 16302 dom.addClass(elm, value); 16303 }); 16304 } 16305 }; 16306 function adjustSelectionToVisibleSelection() { 16307 function findSelectionEnd(start, end) { 16308 var walker = new TreeWalker(end); 16309 for (node = walker.current(); node; node = walker.prev()) { 16310 if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') { 16311 return node; 16312 } 16313 } 16314 }; 16315 16316 // Adjust selection so that a end container with a end offset of zero is not included in the selection 16317 // as this isn't visible to the user. 16318 var rng = ed.selection.getRng(); 16319 var start = rng.startContainer; 16320 var end = rng.endContainer; 16321 16322 if (start != end && rng.endOffset === 0) { 16323 var newEnd = findSelectionEnd(start, end); 16324 var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length; 16325 16326 rng.setEnd(newEnd, endOffset); 16327 } 16328 16329 return rng; 16330 } 16331 16332 function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){ 16333 var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm; 16334 16335 // find the index of the first child list. 16336 each(node.childNodes, function(n, index) { 16337 if (n.nodeName === "UL" || n.nodeName === "OL") { 16338 listIndex = index; 16339 list = n; 16340 return false; 16341 } 16342 }); 16343 16344 // get the index of the bookmarks 16345 each(node.childNodes, function(n, index) { 16346 if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") { 16347 if (n.id == bookmark.id + "_start") { 16348 startIndex = index; 16349 } else if (n.id == bookmark.id + "_end") { 16350 endIndex = index; 16351 } 16352 } 16353 }); 16354 16355 // if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally 16356 if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) { 16357 each(tinymce.grep(node.childNodes), process); 16358 return 0; 16359 } else { 16360 currentWrapElm = dom.clone(wrapElm, FALSE); 16361 16362 // create a list of the nodes on the same side of the list as the selection 16363 each(tinymce.grep(node.childNodes), function(n, index) { 16364 if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) { 16365 nodes.push(n); 16366 n.parentNode.removeChild(n); 16367 } 16368 }); 16369 16370 // insert the wrapping element either before or after the list. 16371 if (startIndex < listIndex) { 16372 node.insertBefore(currentWrapElm, list); 16373 } else if (startIndex > listIndex) { 16374 node.insertBefore(currentWrapElm, list.nextSibling); 16375 } 16376 16377 // add the new nodes to the list. 16378 newWrappers.push(currentWrapElm); 16379 16380 each(nodes, function(node) { 16381 currentWrapElm.appendChild(node); 16382 }); 16383 16384 return currentWrapElm; 16385 } 16386 }; 16387 16388 function applyRngStyle(rng, bookmark, node_specific) { 16389 var newWrappers = [], wrapName, wrapElm, contentEditable = true; 16390 16391 // Setup wrapper element 16392 wrapName = format.inline || format.block; 16393 wrapElm = dom.create(wrapName); 16394 setElementFormat(wrapElm); 16395 16396 rangeUtils.walk(rng, function(nodes) { 16397 var currentWrapElm; 16398 16399 function process(node) { 16400 var nodeName, parentName, found, hasContentEditableState, lastContentEditable; 16401 16402 lastContentEditable = contentEditable; 16403 nodeName = node.nodeName.toLowerCase(); 16404 parentName = node.parentNode.nodeName.toLowerCase(); 16405 16406 // Node has a contentEditable value 16407 if (node.nodeType === 1 && getContentEditable(node)) { 16408 lastContentEditable = contentEditable; 16409 contentEditable = getContentEditable(node) === "true"; 16410 hasContentEditableState = true; // We don't want to wrap the container only it's children 16411 } 16412 16413 // Stop wrapping on br elements 16414 if (isEq(nodeName, 'br')) { 16415 currentWrapElm = 0; 16416 16417 // Remove any br elements when we wrap things 16418 if (format.block) 16419 dom.remove(node); 16420 16421 return; 16422 } 16423 16424 // If node is wrapper type 16425 if (format.wrapper && matchNode(node, name, vars)) { 16426 currentWrapElm = 0; 16427 return; 16428 } 16429 16430 // Can we rename the block 16431 if (contentEditable && !hasContentEditableState && format.block && !format.wrapper && isTextBlock(nodeName)) { 16432 node = dom.rename(node, wrapName); 16433 setElementFormat(node); 16434 newWrappers.push(node); 16435 currentWrapElm = 0; 16436 return; 16437 } 16438 16439 // Handle selector patterns 16440 if (format.selector) { 16441 // Look for matching formats 16442 each(formatList, function(format) { 16443 // Check collapsed state if it exists 16444 if ('collapsed' in format && format.collapsed !== isCollapsed) { 16445 return; 16446 } 16447 16448 if (dom.is(node, format.selector) && !isCaretNode(node)) { 16449 setElementFormat(node, format); 16450 found = true; 16451 } 16452 }); 16453 16454 // Continue processing if a selector match wasn't found and a inline element is defined 16455 if (!format.inline || found) { 16456 currentWrapElm = 0; 16457 return; 16458 } 16459 } 16460 16461 // Is it valid to wrap this item 16462 if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) && 16463 !(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && !isCaretNode(node)) { 16464 // Start wrapping 16465 if (!currentWrapElm) { 16466 // Wrap the node 16467 currentWrapElm = dom.clone(wrapElm, FALSE); 16468 node.parentNode.insertBefore(currentWrapElm, node); 16469 newWrappers.push(currentWrapElm); 16470 } 16471 16472 currentWrapElm.appendChild(node); 16473 } else if (nodeName == 'li' && bookmark) { 16474 // Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element. 16475 currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process); 16476 } else { 16477 // Start a new wrapper for possible children 16478 currentWrapElm = 0; 16479 16480 each(tinymce.grep(node.childNodes), process); 16481 16482 if (hasContentEditableState) { 16483 contentEditable = lastContentEditable; // Restore last contentEditable state from stack 16484 } 16485 16486 // End the last wrapper 16487 currentWrapElm = 0; 16488 } 16489 }; 16490 16491 // Process siblings from range 16492 each(nodes, process); 16493 }); 16494 16495 // Wrap links inside as well, for example color inside a link when the wrapper is around the link 16496 if (format.wrap_links === false) { 16497 each(newWrappers, function(node) { 16498 function process(node) { 16499 var i, currentWrapElm, children; 16500 16501 if (node.nodeName === 'A') { 16502 currentWrapElm = dom.clone(wrapElm, FALSE); 16503 newWrappers.push(currentWrapElm); 16504 16505 children = tinymce.grep(node.childNodes); 16506 for (i = 0; i < children.length; i++) 16507 currentWrapElm.appendChild(children[i]); 16508 16509 node.appendChild(currentWrapElm); 16510 } 16511 16512 each(tinymce.grep(node.childNodes), process); 16513 }; 16514 16515 process(node); 16516 }); 16517 } 16518 16519 // Cleanup 16520 16521 each(newWrappers, function(node) { 16522 var childCount; 16523 16524 function getChildCount(node) { 16525 var count = 0; 16526 16527 each(node.childNodes, function(node) { 16528 if (!isWhiteSpaceNode(node) && !isBookmarkNode(node)) 16529 count++; 16530 }); 16531 16532 return count; 16533 }; 16534 16535 function mergeStyles(node) { 16536 var child, clone; 16537 16538 each(node.childNodes, function(node) { 16539 if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) { 16540 child = node; 16541 return FALSE; // break loop 16542 } 16543 }); 16544 16545 // If child was found and of the same type as the current node 16546 if (child && matchName(child, format)) { 16547 clone = dom.clone(child, FALSE); 16548 setElementFormat(clone); 16549 16550 dom.replace(clone, node, TRUE); 16551 dom.remove(child, 1); 16552 } 16553 16554 return clone || node; 16555 }; 16556 16557 childCount = getChildCount(node); 16558 16559 // Remove empty nodes but only if there is multiple wrappers and they are not block 16560 // elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at 16561 if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) { 16562 dom.remove(node, 1); 16563 return; 16564 } 16565 16566 if (format.inline || format.wrapper) { 16567 // Merges the current node with it's children of similar type to reduce the number of elements 16568 if (!format.exact && childCount === 1) 16569 node = mergeStyles(node); 16570 16571 // Remove/merge children 16572 each(formatList, function(format) { 16573 // Merge all children of similar type will move styles from child to parent 16574 // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span> 16575 // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span> 16576 each(dom.select(format.inline, node), function(child) { 16577 var parent; 16578 16579 // When wrap_links is set to false we don't want 16580 // to remove the format on children within links 16581 if (format.wrap_links === false) { 16582 parent = child.parentNode; 16583 16584 do { 16585 if (parent.nodeName === 'A') 16586 return; 16587 } while (parent = parent.parentNode); 16588 } 16589 16590 removeFormat(format, vars, child, format.exact ? child : null); 16591 }); 16592 }); 16593 16594 // Remove child if direct parent is of same type 16595 if (matchNode(node.parentNode, name, vars)) { 16596 dom.remove(node, 1); 16597 node = 0; 16598 return TRUE; 16599 } 16600 16601 // Look for parent with similar style format 16602 if (format.merge_with_parents) { 16603 dom.getParent(node.parentNode, function(parent) { 16604 if (matchNode(parent, name, vars)) { 16605 dom.remove(node, 1); 16606 node = 0; 16607 return TRUE; 16608 } 16609 }); 16610 } 16611 16612 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b> 16613 if (node && format.merge_siblings !== false) { 16614 node = mergeSiblings(getNonWhiteSpaceSibling(node), node); 16615 node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE)); 16616 } 16617 } 16618 }); 16619 }; 16620 16621 if (format) { 16622 if (node) { 16623 if (node.nodeType) { 16624 rng = dom.createRng(); 16625 rng.setStartBefore(node); 16626 rng.setEndAfter(node); 16627 applyRngStyle(expandRng(rng, formatList), null, true); 16628 } else { 16629 applyRngStyle(node, null, true); 16630 } 16631 } else { 16632 if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) { 16633 // Obtain selection node before selection is unselected by applyRngStyle() 16634 var curSelNode = ed.selection.getNode(); 16635 16636 // If the formats have a default block and we can't find a parent block then start wrapping it with a DIV this is for forced_root_blocks: false 16637 // It's kind of a hack but people should be using the default block type P since all desktop editors work that way 16638 if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) { 16639 apply(formatList[0].defaultBlock); 16640 } 16641 16642 // Apply formatting to selection 16643 ed.selection.setRng(adjustSelectionToVisibleSelection()); 16644 bookmark = selection.getBookmark(); 16645 applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark); 16646 16647 // Colored nodes should be underlined so that the color of the underline matches the text color. 16648 if (format.styles && (format.styles.color || format.styles.textDecoration)) { 16649 tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes'); 16650 processUnderlineAndColor(curSelNode); 16651 } 16652 16653 selection.moveToBookmark(bookmark); 16654 moveStart(selection.getRng(TRUE)); 16655 ed.nodeChanged(); 16656 } else 16657 performCaretAction('apply', name, vars); 16658 } 16659 } 16660 }; 16661 16662 function remove(name, vars, node) { 16663 var formatList = get(name), format = formatList[0], bookmark, i, rng, contentEditable = true; 16664 16665 // Merges the styles for each node 16666 function process(node) { 16667 var children, i, l, localContentEditable, lastContentEditable, hasContentEditableState; 16668 16669 // Node has a contentEditable value 16670 if (node.nodeType === 1 && getContentEditable(node)) { 16671 lastContentEditable = contentEditable; 16672 contentEditable = getContentEditable(node) === "true"; 16673 hasContentEditableState = true; // We don't want to wrap the container only it's children 16674 } 16675 16676 // Grab the children first since the nodelist might be changed 16677 children = tinymce.grep(node.childNodes); 16678 16679 // Process current node 16680 if (contentEditable && !hasContentEditableState) { 16681 for (i = 0, l = formatList.length; i < l; i++) { 16682 if (removeFormat(formatList[i], vars, node, node)) 16683 break; 16684 } 16685 } 16686 16687 // Process the children 16688 if (format.deep) { 16689 if (children.length) { 16690 for (i = 0, l = children.length; i < l; i++) 16691 process(children[i]); 16692 16693 if (hasContentEditableState) { 16694 contentEditable = lastContentEditable; // Restore last contentEditable state from stack 16695 } 16696 } 16697 } 16698 }; 16699 16700 function findFormatRoot(container) { 16701 var formatRoot; 16702 16703 // Find format root 16704 each(getParents(container.parentNode).reverse(), function(parent) { 16705 var format; 16706 16707 // Find format root element 16708 if (!formatRoot && parent.id != '_start' && parent.id != '_end') { 16709 // Is the node matching the format we are looking for 16710 format = matchNode(parent, name, vars); 16711 if (format && format.split !== false) 16712 formatRoot = parent; 16713 } 16714 }); 16715 16716 return formatRoot; 16717 }; 16718 16719 function wrapAndSplit(format_root, container, target, split) { 16720 var parent, clone, lastClone, firstClone, i, formatRootParent; 16721 16722 // Format root found then clone formats and split it 16723 if (format_root) { 16724 formatRootParent = format_root.parentNode; 16725 16726 for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) { 16727 clone = dom.clone(parent, FALSE); 16728 16729 for (i = 0; i < formatList.length; i++) { 16730 if (removeFormat(formatList[i], vars, clone, clone)) { 16731 clone = 0; 16732 break; 16733 } 16734 } 16735 16736 // Build wrapper node 16737 if (clone) { 16738 if (lastClone) 16739 clone.appendChild(lastClone); 16740 16741 if (!firstClone) 16742 firstClone = clone; 16743 16744 lastClone = clone; 16745 } 16746 } 16747 16748 // Never split block elements if the format is mixed 16749 if (split && (!format.mixed || !isBlock(format_root))) 16750 container = dom.split(format_root, container); 16751 16752 // Wrap container in cloned formats 16753 if (lastClone) { 16754 target.parentNode.insertBefore(lastClone, target); 16755 firstClone.appendChild(target); 16756 } 16757 } 16758 16759 return container; 16760 }; 16761 16762 function splitToFormatRoot(container) { 16763 return wrapAndSplit(findFormatRoot(container), container, container, true); 16764 }; 16765 16766 function unwrap(start) { 16767 var node = dom.get(start ? '_start' : '_end'), 16768 out = node[start ? 'firstChild' : 'lastChild']; 16769 16770 // If the end is placed within the start the result will be removed 16771 // So this checks if the out node is a bookmark node if it is it 16772 // checks for another more suitable node 16773 if (isBookmarkNode(out)) 16774 out = out[start ? 'firstChild' : 'lastChild']; 16775 16776 dom.remove(node, true); 16777 16778 return out; 16779 }; 16780 16781 function removeRngStyle(rng) { 16782 var startContainer, endContainer, node; 16783 16784 rng = expandRng(rng, formatList, TRUE); 16785 16786 if (format.split) { 16787 startContainer = getContainer(rng, TRUE); 16788 endContainer = getContainer(rng); 16789 16790 if (startContainer != endContainer) { 16791 // WebKit will render the table incorrectly if we wrap a TD in a SPAN so lets see if the can use the first child instead 16792 // This will happen if you tripple click a table cell and use remove formatting 16793 if (/^(TR|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) { 16794 startContainer = (startContainer.nodeName == "TD" ? startContainer.firstChild : startContainer.firstChild.firstChild) || startContainer; 16795 } 16796 16797 // Wrap start/end nodes in span element since these might be cloned/moved 16798 startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'}); 16799 endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'}); 16800 16801 // Split start/end 16802 splitToFormatRoot(startContainer); 16803 splitToFormatRoot(endContainer); 16804 16805 // Unwrap start/end to get real elements again 16806 startContainer = unwrap(TRUE); 16807 endContainer = unwrap(); 16808 } else 16809 startContainer = endContainer = splitToFormatRoot(startContainer); 16810 16811 // Update range positions since they might have changed after the split operations 16812 rng.startContainer = startContainer.parentNode; 16813 rng.startOffset = nodeIndex(startContainer); 16814 rng.endContainer = endContainer.parentNode; 16815 rng.endOffset = nodeIndex(endContainer) + 1; 16816 } 16817 16818 // Remove items between start/end 16819 rangeUtils.walk(rng, function(nodes) { 16820 each(nodes, function(node) { 16821 process(node); 16822 16823 // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined. 16824 if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') { 16825 removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node); 16826 } 16827 }); 16828 }); 16829 }; 16830 16831 // Handle node 16832 if (node) { 16833 if (node.nodeType) { 16834 rng = dom.createRng(); 16835 rng.setStartBefore(node); 16836 rng.setEndAfter(node); 16837 removeRngStyle(rng); 16838 } else { 16839 removeRngStyle(node); 16840 } 16841 16842 return; 16843 } 16844 16845 if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) { 16846 bookmark = selection.getBookmark(); 16847 removeRngStyle(selection.getRng(TRUE)); 16848 selection.moveToBookmark(bookmark); 16849 16850 // Check if start element still has formatting then we are at: "<b>text|</b>text" and need to move the start into the next text node 16851 if (format.inline && match(name, vars, selection.getStart())) { 16852 moveStart(selection.getRng(true)); 16853 } 16854 16855 ed.nodeChanged(); 16856 } else 16857 performCaretAction('remove', name, vars); 16858 }; 16859 16860 function toggle(name, vars, node) { 16861 var fmt = get(name); 16862 16863 if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle)) 16864 remove(name, vars, node); 16865 else 16866 apply(name, vars, node); 16867 }; 16868 16869 function matchNode(node, name, vars, similar) { 16870 var formatList = get(name), format, i, classes; 16871 16872 function matchItems(node, format, item_name) { 16873 var key, value, items = format[item_name], i; 16874 16875 // Custom match 16876 if (format.onmatch) { 16877 return format.onmatch(node, format, item_name); 16878 } 16879 16880 // Check all items 16881 if (items) { 16882 // Non indexed object 16883 if (items.length === undef) { 16884 for (key in items) { 16885 if (items.hasOwnProperty(key)) { 16886 if (item_name === 'attributes') 16887 value = dom.getAttrib(node, key); 16888 else 16889 value = getStyle(node, key); 16890 16891 if (similar && !value && !format.exact) 16892 return; 16893 16894 if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars))) 16895 return; 16896 } 16897 } 16898 } else { 16899 // Only one match needed for indexed arrays 16900 for (i = 0; i < items.length; i++) { 16901 if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i])) 16902 return format; 16903 } 16904 } 16905 } 16906 16907 return format; 16908 }; 16909 16910 if (formatList && node) { 16911 // Check each format in list 16912 for (i = 0; i < formatList.length; i++) { 16913 format = formatList[i]; 16914 16915 // Name name, attributes, styles and classes 16916 if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) { 16917 // Match classes 16918 if (classes = format.classes) { 16919 for (i = 0; i < classes.length; i++) { 16920 if (!dom.hasClass(node, classes[i])) 16921 return; 16922 } 16923 } 16924 16925 return format; 16926 } 16927 } 16928 } 16929 }; 16930 16931 function match(name, vars, node) { 16932 var startNode; 16933 16934 function matchParents(node) { 16935 // Find first node with similar format settings 16936 node = dom.getParent(node, function(node) { 16937 return !!matchNode(node, name, vars, true); 16938 }); 16939 16940 // Do an exact check on the similar format element 16941 return matchNode(node, name, vars); 16942 }; 16943 16944 // Check specified node 16945 if (node) 16946 return matchParents(node); 16947 16948 // Check selected node 16949 node = selection.getNode(); 16950 if (matchParents(node)) 16951 return TRUE; 16952 16953 // Check start node if it's different 16954 startNode = selection.getStart(); 16955 if (startNode != node) { 16956 if (matchParents(startNode)) 16957 return TRUE; 16958 } 16959 16960 return FALSE; 16961 }; 16962 16963 function matchAll(names, vars) { 16964 var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name; 16965 16966 // Check start of selection for formats 16967 startElement = selection.getStart(); 16968 dom.getParent(startElement, function(node) { 16969 var i, name; 16970 16971 for (i = 0; i < names.length; i++) { 16972 name = names[i]; 16973 16974 if (!checkedMap[name] && matchNode(node, name, vars)) { 16975 checkedMap[name] = true; 16976 matchedFormatNames.push(name); 16977 } 16978 } 16979 }, dom.getRoot()); 16980 16981 return matchedFormatNames; 16982 }; 16983 16984 function canApply(name) { 16985 var formatList = get(name), startNode, parents, i, x, selector; 16986 16987 if (formatList) { 16988 startNode = selection.getStart(); 16989 parents = getParents(startNode); 16990 16991 for (x = formatList.length - 1; x >= 0; x--) { 16992 selector = formatList[x].selector; 16993 16994 // Format is not selector based, then always return TRUE 16995 if (!selector) 16996 return TRUE; 16997 16998 for (i = parents.length - 1; i >= 0; i--) { 16999 if (dom.is(parents[i], selector)) 17000 return TRUE; 17001 } 17002 } 17003 } 17004 17005 return FALSE; 17006 }; 17007 17008 function formatChanged(formats, callback) { 17009 var currentFormats; 17010 17011 // Setup format node change logic 17012 if (!formatChangeData) { 17013 formatChangeData = {}; 17014 currentFormats = {}; 17015 17016 ed.onNodeChange.addToTop(function(ed, cm, node) { 17017 var parents = getParents(node), matchedFormats = {}; 17018 17019 // Check for new formats 17020 each(formatChangeData, function(callbacks, format) { 17021 each(parents, function(node) { 17022 if (matchNode(node, format, {}, true)) { 17023 if (!currentFormats[format]) { 17024 // Execute callbacks 17025 each(callbacks, function(callback) { 17026 callback(true, {node: node, format: format, parents: parents}); 17027 }); 17028 17029 currentFormats[format] = callbacks; 17030 } 17031 17032 matchedFormats[format] = callbacks; 17033 return false; 17034 } 17035 }); 17036 }); 17037 17038 // Check if current formats still match 17039 each(currentFormats, function(callbacks, format) { 17040 if (!matchedFormats[format]) { 17041 delete currentFormats[format]; 17042 17043 each(callbacks, function(callback) { 17044 callback(false, {node: node, format: format, parents: parents}); 17045 }); 17046 } 17047 }); 17048 }); 17049 } 17050 17051 // Add format listeners 17052 each(formats.split(','), function(format) { 17053 if (!formatChangeData[format]) { 17054 formatChangeData[format] = []; 17055 } 17056 17057 formatChangeData[format].push(callback); 17058 }); 17059 17060 return this; 17061 }; 17062 17063 // Expose to public 17064 tinymce.extend(this, { 17065 get : get, 17066 register : register, 17067 apply : apply, 17068 remove : remove, 17069 toggle : toggle, 17070 match : match, 17071 matchAll : matchAll, 17072 matchNode : matchNode, 17073 canApply : canApply, 17074 formatChanged: formatChanged 17075 }); 17076 17077 // Initialize 17078 defaultFormats(); 17079 addKeyboardShortcuts(); 17080 17081 // Private functions 17082 17083 function matchName(node, format) { 17084 // Check for inline match 17085 if (isEq(node, format.inline)) 17086 return TRUE; 17087 17088 // Check for block match 17089 if (isEq(node, format.block)) 17090 return TRUE; 17091 17092 // Check for selector match 17093 if (format.selector) 17094 return dom.is(node, format.selector); 17095 }; 17096 17097 function isEq(str1, str2) { 17098 str1 = str1 || ''; 17099 str2 = str2 || ''; 17100 17101 str1 = '' + (str1.nodeName || str1); 17102 str2 = '' + (str2.nodeName || str2); 17103 17104 return str1.toLowerCase() == str2.toLowerCase(); 17105 }; 17106 17107 function getStyle(node, name) { 17108 var styleVal = dom.getStyle(node, name); 17109 17110 // Force the format to hex 17111 if (name == 'color' || name == 'backgroundColor') 17112 styleVal = dom.toHex(styleVal); 17113 17114 // Opera will return bold as 700 17115 if (name == 'fontWeight' && styleVal == 700) 17116 styleVal = 'bold'; 17117 17118 return '' + styleVal; 17119 }; 17120 17121 function replaceVars(value, vars) { 17122 if (typeof(value) != "string") 17123 value = value(vars); 17124 else if (vars) { 17125 value = value.replace(/%(\w+)/g, function(str, name) { 17126 return vars[name] || str; 17127 }); 17128 } 17129 17130 return value; 17131 }; 17132 17133 function isWhiteSpaceNode(node) { 17134 return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue); 17135 }; 17136 17137 function wrap(node, name, attrs) { 17138 var wrapper = dom.create(name, attrs); 17139 17140 node.parentNode.insertBefore(wrapper, node); 17141 wrapper.appendChild(node); 17142 17143 return wrapper; 17144 }; 17145 17146 function expandRng(rng, format, remove) { 17147 var sibling, lastIdx, leaf, endPoint, 17148 startContainer = rng.startContainer, 17149 startOffset = rng.startOffset, 17150 endContainer = rng.endContainer, 17151 endOffset = rng.endOffset; 17152 17153 // This function walks up the tree if there is no siblings before/after the node 17154 function findParentContainer(start) { 17155 var container, parent, child, sibling, siblingName, root; 17156 17157 container = parent = start ? startContainer : endContainer; 17158 siblingName = start ? 'previousSibling' : 'nextSibling'; 17159 root = dom.getRoot(); 17160 17161 // If it's a text node and the offset is inside the text 17162 if (container.nodeType == 3 && !isWhiteSpaceNode(container)) { 17163 if (start ? startOffset > 0 : endOffset < container.nodeValue.length) { 17164 return container; 17165 } 17166 } 17167 17168 for (;;) { 17169 // Stop expanding on block elements 17170 if (!format[0].block_expand && isBlock(parent)) 17171 return parent; 17172 17173 // Walk left/right 17174 for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) { 17175 if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling)) { 17176 return parent; 17177 } 17178 } 17179 17180 // Check if we can move up are we at root level or body level 17181 if (parent.parentNode == root) { 17182 container = parent; 17183 break; 17184 } 17185 17186 parent = parent.parentNode; 17187 } 17188 17189 return container; 17190 }; 17191 17192 // This function walks down the tree to find the leaf at the selection. 17193 // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node. 17194 function findLeaf(node, offset) { 17195 if (offset === undef) 17196 offset = node.nodeType === 3 ? node.length : node.childNodes.length; 17197 while (node && node.hasChildNodes()) { 17198 node = node.childNodes[offset]; 17199 if (node) 17200 offset = node.nodeType === 3 ? node.length : node.childNodes.length; 17201 } 17202 return { node: node, offset: offset }; 17203 } 17204 17205 // If index based start position then resolve it 17206 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) { 17207 lastIdx = startContainer.childNodes.length - 1; 17208 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset]; 17209 17210 if (startContainer.nodeType == 3) 17211 startOffset = 0; 17212 } 17213 17214 // If index based end position then resolve it 17215 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) { 17216 lastIdx = endContainer.childNodes.length - 1; 17217 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1]; 17218 17219 if (endContainer.nodeType == 3) 17220 endOffset = endContainer.nodeValue.length; 17221 } 17222 17223 // Expands the node to the closes contentEditable false element if it exists 17224 function findParentContentEditable(node) { 17225 var parent = node; 17226 17227 while (parent) { 17228 if (parent.nodeType === 1 && getContentEditable(parent)) { 17229 return getContentEditable(parent) === "false" ? parent : node; 17230 } 17231 17232 parent = parent.parentNode; 17233 } 17234 17235 return node; 17236 }; 17237 17238 function findWordEndPoint(container, offset, start) { 17239 var walker, node, pos, lastTextNode; 17240 17241 function findSpace(node, offset) { 17242 var pos, pos2, str = node.nodeValue; 17243 17244 if (typeof(offset) == "undefined") { 17245 offset = start ? str.length : 0; 17246 } 17247 17248 if (start) { 17249 pos = str.lastIndexOf(' ', offset); 17250 pos2 = str.lastIndexOf('\u00a0', offset); 17251 pos = pos > pos2 ? pos : pos2; 17252 17253 // Include the space on remove to avoid tag soup 17254 if (pos !== -1 && !remove) { 17255 pos++; 17256 } 17257 } else { 17258 pos = str.indexOf(' ', offset); 17259 pos2 = str.indexOf('\u00a0', offset); 17260 pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2; 17261 } 17262 17263 return pos; 17264 }; 17265 17266 if (container.nodeType === 3) { 17267 pos = findSpace(container, offset); 17268 17269 if (pos !== -1) { 17270 return {container : container, offset : pos}; 17271 } 17272 17273 lastTextNode = container; 17274 } 17275 17276 // Walk the nodes inside the block 17277 walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody()); 17278 while (node = walker[start ? 'prev' : 'next']()) { 17279 if (node.nodeType === 3) { 17280 lastTextNode = node; 17281 pos = findSpace(node); 17282 17283 if (pos !== -1) { 17284 return {container : node, offset : pos}; 17285 } 17286 } else if (isBlock(node)) { 17287 break; 17288 } 17289 } 17290 17291 if (lastTextNode) { 17292 if (start) { 17293 offset = 0; 17294 } else { 17295 offset = lastTextNode.length; 17296 } 17297 17298 return {container: lastTextNode, offset: offset}; 17299 } 17300 }; 17301 17302 function findSelectorEndPoint(container, sibling_name) { 17303 var parents, i, y, curFormat; 17304 17305 if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name]) 17306 container = container[sibling_name]; 17307 17308 parents = getParents(container); 17309 for (i = 0; i < parents.length; i++) { 17310 for (y = 0; y < format.length; y++) { 17311 curFormat = format[y]; 17312 17313 // If collapsed state is set then skip formats that doesn't match that 17314 if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed) 17315 continue; 17316 17317 if (dom.is(parents[i], curFormat.selector)) 17318 return parents[i]; 17319 } 17320 } 17321 17322 return container; 17323 }; 17324 17325 function findBlockEndPoint(container, sibling_name, sibling_name2) { 17326 var node; 17327 17328 // Expand to block of similar type 17329 if (!format[0].wrapper) 17330 node = dom.getParent(container, format[0].block); 17331 17332 // Expand to first wrappable block element or any block element 17333 if (!node) 17334 node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock); 17335 17336 // Exclude inner lists from wrapping 17337 if (node && format[0].wrapper) 17338 node = getParents(node, 'ul,ol').reverse()[0] || node; 17339 17340 // Didn't find a block element look for first/last wrappable element 17341 if (!node) { 17342 node = container; 17343 17344 while (node[sibling_name] && !isBlock(node[sibling_name])) { 17345 node = node[sibling_name]; 17346 17347 // Break on BR but include it will be removed later on 17348 // we can't remove it now since we need to check if it can be wrapped 17349 if (isEq(node, 'br')) 17350 break; 17351 } 17352 } 17353 17354 return node || container; 17355 }; 17356 17357 // Expand to closest contentEditable element 17358 startContainer = findParentContentEditable(startContainer); 17359 endContainer = findParentContentEditable(endContainer); 17360 17361 // Exclude bookmark nodes if possible 17362 if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) { 17363 startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode; 17364 startContainer = startContainer.nextSibling || startContainer; 17365 17366 if (startContainer.nodeType == 3) 17367 startOffset = 0; 17368 } 17369 17370 if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) { 17371 endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode; 17372 endContainer = endContainer.previousSibling || endContainer; 17373 17374 if (endContainer.nodeType == 3) 17375 endOffset = endContainer.length; 17376 } 17377 17378 if (format[0].inline) { 17379 if (rng.collapsed) { 17380 // Expand left to closest word boundery 17381 endPoint = findWordEndPoint(startContainer, startOffset, true); 17382 if (endPoint) { 17383 startContainer = endPoint.container; 17384 startOffset = endPoint.offset; 17385 } 17386 17387 // Expand right to closest word boundery 17388 endPoint = findWordEndPoint(endContainer, endOffset); 17389 if (endPoint) { 17390 endContainer = endPoint.container; 17391 endOffset = endPoint.offset; 17392 } 17393 } 17394 17395 // Avoid applying formatting to a trailing space. 17396 leaf = findLeaf(endContainer, endOffset); 17397 if (leaf.node) { 17398 while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling) 17399 leaf = findLeaf(leaf.node.previousSibling); 17400 17401 if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 && 17402 leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') { 17403 17404 if (leaf.offset > 1) { 17405 endContainer = leaf.node; 17406 endContainer.splitText(leaf.offset - 1); 17407 } 17408 } 17409 } 17410 } 17411 17412 // Move start/end point up the tree if the leaves are sharp and if we are in different containers 17413 // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>! 17414 // This will reduce the number of wrapper elements that needs to be created 17415 // Move start point up the tree 17416 if (format[0].inline || format[0].block_expand) { 17417 if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) { 17418 startContainer = findParentContainer(true); 17419 } 17420 17421 if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) { 17422 endContainer = findParentContainer(); 17423 } 17424 } 17425 17426 // Expand start/end container to matching selector 17427 if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) { 17428 // Find new startContainer/endContainer if there is better one 17429 startContainer = findSelectorEndPoint(startContainer, 'previousSibling'); 17430 endContainer = findSelectorEndPoint(endContainer, 'nextSibling'); 17431 } 17432 17433 // Expand start/end container to matching block element or text node 17434 if (format[0].block || format[0].selector) { 17435 // Find new startContainer/endContainer if there is better one 17436 startContainer = findBlockEndPoint(startContainer, 'previousSibling'); 17437 endContainer = findBlockEndPoint(endContainer, 'nextSibling'); 17438 17439 // Non block element then try to expand up the leaf 17440 if (format[0].block) { 17441 if (!isBlock(startContainer)) 17442 startContainer = findParentContainer(true); 17443 17444 if (!isBlock(endContainer)) 17445 endContainer = findParentContainer(); 17446 } 17447 } 17448 17449 // Setup index for startContainer 17450 if (startContainer.nodeType == 1) { 17451 startOffset = nodeIndex(startContainer); 17452 startContainer = startContainer.parentNode; 17453 } 17454 17455 // Setup index for endContainer 17456 if (endContainer.nodeType == 1) { 17457 endOffset = nodeIndex(endContainer) + 1; 17458 endContainer = endContainer.parentNode; 17459 } 17460 17461 // Return new range like object 17462 return { 17463 startContainer : startContainer, 17464 startOffset : startOffset, 17465 endContainer : endContainer, 17466 endOffset : endOffset 17467 }; 17468 } 17469 17470 function removeFormat(format, vars, node, compare_node) { 17471 var i, attrs, stylesModified; 17472 17473 // Check if node matches format 17474 if (!matchName(node, format)) 17475 return FALSE; 17476 17477 // Should we compare with format attribs and styles 17478 if (format.remove != 'all') { 17479 // Remove styles 17480 each(format.styles, function(value, name) { 17481 value = replaceVars(value, vars); 17482 17483 // Indexed array 17484 if (typeof(name) === 'number') { 17485 name = value; 17486 compare_node = 0; 17487 } 17488 17489 if (!compare_node || isEq(getStyle(compare_node, name), value)) 17490 dom.setStyle(node, name, ''); 17491 17492 stylesModified = 1; 17493 }); 17494 17495 // Remove style attribute if it's empty 17496 if (stylesModified && dom.getAttrib(node, 'style') == '') { 17497 node.removeAttribute('style'); 17498 node.removeAttribute('data-mce-style'); 17499 } 17500 17501 // Remove attributes 17502 each(format.attributes, function(value, name) { 17503 var valueOut; 17504 17505 value = replaceVars(value, vars); 17506 17507 // Indexed array 17508 if (typeof(name) === 'number') { 17509 name = value; 17510 compare_node = 0; 17511 } 17512 17513 if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) { 17514 // Keep internal classes 17515 if (name == 'class') { 17516 value = dom.getAttrib(node, name); 17517 if (value) { 17518 // Build new class value where everything is removed except the internal prefixed classes 17519 valueOut = ''; 17520 each(value.split(/\s+/), function(cls) { 17521 if (/mce\w+/.test(cls)) 17522 valueOut += (valueOut ? ' ' : '') + cls; 17523 }); 17524 17525 // We got some internal classes left 17526 if (valueOut) { 17527 dom.setAttrib(node, name, valueOut); 17528 return; 17529 } 17530 } 17531 } 17532 17533 // IE6 has a bug where the attribute doesn't get removed correctly 17534 if (name == "class") 17535 node.removeAttribute('className'); 17536 17537 // Remove mce prefixed attributes 17538 if (MCE_ATTR_RE.test(name)) 17539 node.removeAttribute('data-mce-' + name); 17540 17541 node.removeAttribute(name); 17542 } 17543 }); 17544 17545 // Remove classes 17546 each(format.classes, function(value) { 17547 value = replaceVars(value, vars); 17548 17549 if (!compare_node || dom.hasClass(compare_node, value)) 17550 dom.removeClass(node, value); 17551 }); 17552 17553 // Check for non internal attributes 17554 attrs = dom.getAttribs(node); 17555 for (i = 0; i < attrs.length; i++) { 17556 if (attrs[i].nodeName.indexOf('_') !== 0) 17557 return FALSE; 17558 } 17559 } 17560 17561 // Remove the inline child if it's empty for example <b> or <span> 17562 if (format.remove != 'none') { 17563 removeNode(node, format); 17564 return TRUE; 17565 } 17566 }; 17567 17568 function removeNode(node, format) { 17569 var parentNode = node.parentNode, rootBlockElm; 17570 17571 function find(node, next, inc) { 17572 node = getNonWhiteSpaceSibling(node, next, inc); 17573 17574 return !node || (node.nodeName == 'BR' || isBlock(node)); 17575 }; 17576 17577 if (format.block) { 17578 if (!forcedRootBlock) { 17579 // Append BR elements if needed before we remove the block 17580 if (isBlock(node) && !isBlock(parentNode)) { 17581 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1)) 17582 node.insertBefore(dom.create('br'), node.firstChild); 17583 17584 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1)) 17585 node.appendChild(dom.create('br')); 17586 } 17587 } else { 17588 // Wrap the block in a forcedRootBlock if we are at the root of document 17589 if (parentNode == dom.getRoot()) { 17590 if (!format.list_block || !isEq(node, format.list_block)) { 17591 each(tinymce.grep(node.childNodes), function(node) { 17592 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) { 17593 if (!rootBlockElm) 17594 rootBlockElm = wrap(node, forcedRootBlock); 17595 else 17596 rootBlockElm.appendChild(node); 17597 } else 17598 rootBlockElm = 0; 17599 }); 17600 } 17601 } 17602 } 17603 } 17604 17605 // Never remove nodes that isn't the specified inline element if a selector is specified too 17606 if (format.selector && format.inline && !isEq(format.inline, node)) 17607 return; 17608 17609 dom.remove(node, 1); 17610 }; 17611 17612 function getNonWhiteSpaceSibling(node, next, inc) { 17613 if (node) { 17614 next = next ? 'nextSibling' : 'previousSibling'; 17615 17616 for (node = inc ? node : node[next]; node; node = node[next]) { 17617 if (node.nodeType == 1 || !isWhiteSpaceNode(node)) 17618 return node; 17619 } 17620 } 17621 }; 17622 17623 function isBookmarkNode(node) { 17624 return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark'; 17625 }; 17626 17627 function mergeSiblings(prev, next) { 17628 var marker, sibling, tmpSibling; 17629 17630 function compareElements(node1, node2) { 17631 // Not the same name 17632 if (node1.nodeName != node2.nodeName) 17633 return FALSE; 17634 17635 function getAttribs(node) { 17636 var attribs = {}; 17637 17638 each(dom.getAttribs(node), function(attr) { 17639 var name = attr.nodeName.toLowerCase(); 17640 17641 // Don't compare internal attributes or style 17642 if (name.indexOf('_') !== 0 && name !== 'style') 17643 attribs[name] = dom.getAttrib(node, name); 17644 }); 17645 17646 return attribs; 17647 }; 17648 17649 function compareObjects(obj1, obj2) { 17650 var value, name; 17651 17652 for (name in obj1) { 17653 // Obj1 has item obj2 doesn't have 17654 if (obj1.hasOwnProperty(name)) { 17655 value = obj2[name]; 17656 17657 // Obj2 doesn't have obj1 item 17658 if (value === undef) 17659 return FALSE; 17660 17661 // Obj2 item has a different value 17662 if (obj1[name] != value) 17663 return FALSE; 17664 17665 // Delete similar value 17666 delete obj2[name]; 17667 } 17668 } 17669 17670 // Check if obj 2 has something obj 1 doesn't have 17671 for (name in obj2) { 17672 // Obj2 has item obj1 doesn't have 17673 if (obj2.hasOwnProperty(name)) 17674 return FALSE; 17675 } 17676 17677 return TRUE; 17678 }; 17679 17680 // Attribs are not the same 17681 if (!compareObjects(getAttribs(node1), getAttribs(node2))) 17682 return FALSE; 17683 17684 // Styles are not the same 17685 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) 17686 return FALSE; 17687 17688 return TRUE; 17689 }; 17690 17691 function findElementSibling(node, sibling_name) { 17692 for (sibling = node; sibling; sibling = sibling[sibling_name]) { 17693 if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0) 17694 return node; 17695 17696 if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) 17697 return sibling; 17698 } 17699 17700 return node; 17701 }; 17702 17703 // Check if next/prev exists and that they are elements 17704 if (prev && next) { 17705 // If previous sibling is empty then jump over it 17706 prev = findElementSibling(prev, 'previousSibling'); 17707 next = findElementSibling(next, 'nextSibling'); 17708 17709 // Compare next and previous nodes 17710 if (compareElements(prev, next)) { 17711 // Append nodes between 17712 for (sibling = prev.nextSibling; sibling && sibling != next;) { 17713 tmpSibling = sibling; 17714 sibling = sibling.nextSibling; 17715 prev.appendChild(tmpSibling); 17716 } 17717 17718 // Remove next node 17719 dom.remove(next); 17720 17721 // Move children into prev node 17722 each(tinymce.grep(next.childNodes), function(node) { 17723 prev.appendChild(node); 17724 }); 17725 17726 return prev; 17727 } 17728 } 17729 17730 return next; 17731 }; 17732 17733 function isTextBlock(name) { 17734 return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name); 17735 }; 17736 17737 function getContainer(rng, start) { 17738 var container, offset, lastIdx, walker; 17739 17740 container = rng[start ? 'startContainer' : 'endContainer']; 17741 offset = rng[start ? 'startOffset' : 'endOffset']; 17742 17743 if (container.nodeType == 1) { 17744 lastIdx = container.childNodes.length - 1; 17745 17746 if (!start && offset) 17747 offset--; 17748 17749 container = container.childNodes[offset > lastIdx ? lastIdx : offset]; 17750 } 17751 17752 // If start text node is excluded then walk to the next node 17753 if (container.nodeType === 3 && start && offset >= container.nodeValue.length) { 17754 container = new TreeWalker(container, ed.getBody()).next() || container; 17755 } 17756 17757 // If end text node is excluded then walk to the previous node 17758 if (container.nodeType === 3 && !start && offset === 0) { 17759 container = new TreeWalker(container, ed.getBody()).prev() || container; 17760 } 17761 17762 return container; 17763 }; 17764 17765 function performCaretAction(type, name, vars) { 17766 var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug; 17767 17768 // Creates a caret container bogus element 17769 function createCaretContainer(fill) { 17770 var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''}); 17771 17772 if (fill) { 17773 caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR)); 17774 } 17775 17776 return caretContainer; 17777 }; 17778 17779 function isCaretContainerEmpty(node, nodes) { 17780 while (node) { 17781 if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) { 17782 return false; 17783 } 17784 17785 // Collect nodes 17786 if (nodes && node.nodeType === 1) { 17787 nodes.push(node); 17788 } 17789 17790 node = node.firstChild; 17791 } 17792 17793 return true; 17794 }; 17795 17796 // Returns any parent caret container element 17797 function getParentCaretContainer(node) { 17798 while (node) { 17799 if (node.id === caretContainerId) { 17800 return node; 17801 } 17802 17803 node = node.parentNode; 17804 } 17805 }; 17806 17807 // Finds the first text node in the specified node 17808 function findFirstTextNode(node) { 17809 var walker; 17810 17811 if (node) { 17812 walker = new TreeWalker(node, node); 17813 17814 for (node = walker.current(); node; node = walker.next()) { 17815 if (node.nodeType === 3) { 17816 return node; 17817 } 17818 } 17819 } 17820 }; 17821 17822 // Removes the caret container for the specified node or all on the current document 17823 function removeCaretContainer(node, move_caret) { 17824 var child, rng; 17825 17826 if (!node) { 17827 node = getParentCaretContainer(selection.getStart()); 17828 17829 if (!node) { 17830 while (node = dom.get(caretContainerId)) { 17831 removeCaretContainer(node, false); 17832 } 17833 } 17834 } else { 17835 rng = selection.getRng(true); 17836 17837 if (isCaretContainerEmpty(node)) { 17838 if (move_caret !== false) { 17839 rng.setStartBefore(node); 17840 rng.setEndBefore(node); 17841 } 17842 17843 dom.remove(node); 17844 } else { 17845 child = findFirstTextNode(node); 17846 17847 if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) { 17848 child = child.deleteData(0, 1); 17849 } 17850 17851 dom.remove(node, 1); 17852 } 17853 17854 selection.setRng(rng); 17855 } 17856 }; 17857 17858 // Applies formatting to the caret postion 17859 function applyCaretFormat() { 17860 var rng, caretContainer, textNode, offset, bookmark, container, text; 17861 17862 rng = selection.getRng(true); 17863 offset = rng.startOffset; 17864 container = rng.startContainer; 17865 text = container.nodeValue; 17866 17867 caretContainer = getParentCaretContainer(selection.getStart()); 17868 if (caretContainer) { 17869 textNode = findFirstTextNode(caretContainer); 17870 } 17871 17872 // Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character 17873 if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) { 17874 // Get bookmark of caret position 17875 bookmark = selection.getBookmark(); 17876 17877 // Collapse bookmark range (WebKit) 17878 rng.collapse(true); 17879 17880 // Expand the range to the closest word and split it at those points 17881 rng = expandRng(rng, get(name)); 17882 rng = rangeUtils.split(rng); 17883 17884 // Apply the format to the range 17885 apply(name, vars, rng); 17886 17887 // Move selection back to caret position 17888 selection.moveToBookmark(bookmark); 17889 } else { 17890 if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) { 17891 caretContainer = createCaretContainer(true); 17892 textNode = caretContainer.firstChild; 17893 17894 rng.insertNode(caretContainer); 17895 offset = 1; 17896 17897 apply(name, vars, caretContainer); 17898 } else { 17899 apply(name, vars, caretContainer); 17900 } 17901 17902 // Move selection to text node 17903 selection.setCursorLocation(textNode, offset); 17904 } 17905 }; 17906 17907 function removeCaretFormat() { 17908 var rng = selection.getRng(true), container, offset, bookmark, 17909 hasContentAfter, node, formatNode, parents = [], i, caretContainer; 17910 17911 container = rng.startContainer; 17912 offset = rng.startOffset; 17913 node = container; 17914 17915 if (container.nodeType == 3) { 17916 if (offset != container.nodeValue.length || container.nodeValue === INVISIBLE_CHAR) { 17917 hasContentAfter = true; 17918 } 17919 17920 node = node.parentNode; 17921 } 17922 17923 while (node) { 17924 if (matchNode(node, name, vars)) { 17925 formatNode = node; 17926 break; 17927 } 17928 17929 if (node.nextSibling) { 17930 hasContentAfter = true; 17931 } 17932 17933 parents.push(node); 17934 node = node.parentNode; 17935 } 17936 17937 // Node doesn't have the specified format 17938 if (!formatNode) { 17939 return; 17940 } 17941 17942 // Is there contents after the caret then remove the format on the element 17943 if (hasContentAfter) { 17944 // Get bookmark of caret position 17945 bookmark = selection.getBookmark(); 17946 17947 // Collapse bookmark range (WebKit) 17948 rng.collapse(true); 17949 17950 // Expand the range to the closest word and split it at those points 17951 rng = expandRng(rng, get(name), true); 17952 rng = rangeUtils.split(rng); 17953 17954 // Remove the format from the range 17955 remove(name, vars, rng); 17956 17957 // Move selection back to caret position 17958 selection.moveToBookmark(bookmark); 17959 } else { 17960 caretContainer = createCaretContainer(); 17961 17962 node = caretContainer; 17963 for (i = parents.length - 1; i >= 0; i--) { 17964 node.appendChild(dom.clone(parents[i], false)); 17965 node = node.firstChild; 17966 } 17967 17968 // Insert invisible character into inner most format element 17969 node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR)); 17970 node = node.firstChild; 17971 17972 // Insert caret container after the formated node 17973 dom.insertAfter(caretContainer, formatNode); 17974 17975 // Move selection to text node 17976 selection.setCursorLocation(node, 1); 17977 } 17978 }; 17979 17980 // Checks if the parent caret container node isn't empty if that is the case it 17981 // will remove the bogus state on all children that isn't empty 17982 function unmarkBogusCaretParents() { 17983 var i, caretContainer, node; 17984 17985 caretContainer = getParentCaretContainer(selection.getStart()); 17986 if (caretContainer && !dom.isEmpty(caretContainer)) { 17987 tinymce.walk(caretContainer, function(node) { 17988 if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) { 17989 dom.setAttrib(node, 'data-mce-bogus', null); 17990 } 17991 }, 'childNodes'); 17992 } 17993 }; 17994 17995 // Only bind the caret events once 17996 if (!self._hasCaretEvents) { 17997 // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements 17998 ed.onBeforeGetContent.addToTop(function() { 17999 var nodes = [], i; 18000 18001 if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) { 18002 // Mark children 18003 i = nodes.length; 18004 while (i--) { 18005 dom.setAttrib(nodes[i], 'data-mce-bogus', '1'); 18006 } 18007 } 18008 }); 18009 18010 // Remove caret container on mouse up and on key up 18011 tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) { 18012 ed[name].addToTop(function() { 18013 removeCaretContainer(); 18014 unmarkBogusCaretParents(); 18015 }); 18016 }); 18017 18018 // Remove caret container on keydown and it's a backspace, enter or left/right arrow keys 18019 ed.onKeyDown.addToTop(function(ed, e) { 18020 var keyCode = e.keyCode; 18021 18022 if (keyCode == 8 || keyCode == 37 || keyCode == 39) { 18023 removeCaretContainer(getParentCaretContainer(selection.getStart())); 18024 } 18025 18026 unmarkBogusCaretParents(); 18027 }); 18028 18029 // Remove bogus state if they got filled by contents using editor.selection.setContent 18030 selection.onSetContent.add(unmarkBogusCaretParents); 18031 18032 self._hasCaretEvents = true; 18033 } 18034 18035 // Do apply or remove caret format 18036 if (type == "apply") { 18037 applyCaretFormat(); 18038 } else { 18039 removeCaretFormat(); 18040 } 18041 }; 18042 18043 function moveStart(rng) { 18044 var container = rng.startContainer, 18045 offset = rng.startOffset, isAtEndOfText, 18046 walker, node, nodes, tmpNode; 18047 18048 // Convert text node into index if possible 18049 if (container.nodeType == 3 && offset >= container.nodeValue.length) { 18050 // Get the parent container location and walk from there 18051 offset = nodeIndex(container); 18052 container = container.parentNode; 18053 isAtEndOfText = true; 18054 } 18055 18056 // Move startContainer/startOffset in to a suitable node 18057 if (container.nodeType == 1) { 18058 nodes = container.childNodes; 18059 container = nodes[Math.min(offset, nodes.length - 1)]; 18060 walker = new TreeWalker(container, dom.getParent(container, dom.isBlock)); 18061 18062 // If offset is at end of the parent node walk to the next one 18063 if (offset > nodes.length - 1 || isAtEndOfText) 18064 walker.next(); 18065 18066 for (node = walker.current(); node; node = walker.next()) { 18067 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) { 18068 // IE has a "neat" feature where it moves the start node into the closest element 18069 // we can avoid this by inserting an element before it and then remove it after we set the selection 18070 tmpNode = dom.create('a', null, INVISIBLE_CHAR); 18071 node.parentNode.insertBefore(tmpNode, node); 18072 18073 // Set selection and remove tmpNode 18074 rng.setStart(node, 0); 18075 selection.setRng(rng); 18076 dom.remove(tmpNode); 18077 18078 return; 18079 } 18080 } 18081 } 18082 }; 18083 }; 18084 })(tinymce); 18085 18086 tinymce.onAddEditor.add(function(tinymce, ed) { 18087 var filters, fontSizes, dom, settings = ed.settings; 18088 18089 function replaceWithSpan(node, styles) { 18090 tinymce.each(styles, function(value, name) { 18091 if (value) 18092 dom.setStyle(node, name, value); 18093 }); 18094 18095 dom.rename(node, 'span'); 18096 }; 18097 18098 function convert(editor, params) { 18099 dom = editor.dom; 18100 18101 if (settings.convert_fonts_to_spans) { 18102 tinymce.each(dom.select('font,u,strike', params.node), function(node) { 18103 filters[node.nodeName.toLowerCase()](ed.dom, node); 18104 }); 18105 } 18106 }; 18107 18108 if (settings.inline_styles) { 18109 fontSizes = tinymce.explode(settings.font_size_legacy_values); 18110 18111 filters = { 18112 font : function(dom, node) { 18113 replaceWithSpan(node, { 18114 backgroundColor : node.style.backgroundColor, 18115 color : node.color, 18116 fontFamily : node.face, 18117 fontSize : fontSizes[parseInt(node.size, 10) - 1] 18118 }); 18119 }, 18120 18121 u : function(dom, node) { 18122 replaceWithSpan(node, { 18123 textDecoration : 'underline' 18124 }); 18125 }, 18126 18127 strike : function(dom, node) { 18128 replaceWithSpan(node, { 18129 textDecoration : 'line-through' 18130 }); 18131 } 18132 }; 18133 18134 ed.onPreProcess.add(convert); 18135 ed.onSetContent.add(convert); 18136 18137 ed.onInit.add(function() { 18138 ed.selection.onSetContent.add(convert); 18139 }); 18140 } 18141 }); 18142 18143 (function(tinymce) { 18144 var TreeWalker = tinymce.dom.TreeWalker; 18145 18146 tinymce.EnterKey = function(editor) { 18147 var dom = editor.dom, selection = editor.selection, settings = editor.settings, undoManager = editor.undoManager, nonEmptyElementsMap = editor.schema.getNonEmptyElements(); 18148 18149 function handleEnterKey(evt) { 18150 var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode, 18151 newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer; 18152 18153 // Returns true if the block can be split into two blocks or not 18154 function canSplitBlock(node) { 18155 return node && 18156 dom.isBlock(node) && 18157 !/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) && 18158 !/^(fixed|absolute)/i.test(node.style.position) && 18159 dom.getContentEditable(node) !== "true"; 18160 }; 18161 18162 // Renders empty block on IE 18163 function renderBlockOnIE(block) { 18164 var oldRng; 18165 18166 if (tinymce.isIE && dom.isBlock(block)) { 18167 oldRng = selection.getRng(); 18168 block.appendChild(dom.create('span', null, '\u00a0')); 18169 selection.select(block); 18170 block.lastChild.outerHTML = ''; 18171 selection.setRng(oldRng); 18172 } 18173 }; 18174 18175 // Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>x</p> 18176 function trimInlineElementsOnLeftSideOfBlock(block) { 18177 var node = block, firstChilds = [], i; 18178 18179 // Find inner most first child ex: <p><i><b>*</b></i></p> 18180 while (node = node.firstChild) { 18181 if (dom.isBlock(node)) { 18182 return; 18183 } 18184 18185 if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 18186 firstChilds.push(node); 18187 } 18188 } 18189 18190 i = firstChilds.length; 18191 while (i--) { 18192 node = firstChilds[i]; 18193 if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) { 18194 dom.remove(node); 18195 } 18196 } 18197 }; 18198 18199 // Moves the caret to a suitable position within the root for example in the first non pure whitespace text node or before an image 18200 function moveToCaretPosition(root) { 18201 var walker, node, rng, y, viewPort, lastNode = root, tempElm; 18202 18203 rng = dom.createRng(); 18204 18205 if (root.hasChildNodes()) { 18206 walker = new TreeWalker(root, root); 18207 18208 while (node = walker.current()) { 18209 if (node.nodeType == 3) { 18210 rng.setStart(node, 0); 18211 rng.setEnd(node, 0); 18212 break; 18213 } 18214 18215 if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 18216 rng.setStartBefore(node); 18217 rng.setEndBefore(node); 18218 break; 18219 } 18220 18221 lastNode = node; 18222 node = walker.next(); 18223 } 18224 18225 if (!node) { 18226 rng.setStart(lastNode, 0); 18227 rng.setEnd(lastNode, 0); 18228 } 18229 } else { 18230 if (root.nodeName == 'BR') { 18231 if (root.nextSibling && dom.isBlock(root.nextSibling)) { 18232 // Trick on older IE versions to render the caret before the BR between two lists 18233 if (!documentMode || documentMode < 9) { 18234 tempElm = dom.create('br'); 18235 root.parentNode.insertBefore(tempElm, root); 18236 } 18237 18238 rng.setStartBefore(root); 18239 rng.setEndBefore(root); 18240 } else { 18241 rng.setStartAfter(root); 18242 rng.setEndAfter(root); 18243 } 18244 } else { 18245 rng.setStart(root, 0); 18246 rng.setEnd(root, 0); 18247 } 18248 } 18249 18250 selection.setRng(rng); 18251 18252 // Remove tempElm created for old IE:s 18253 dom.remove(tempElm); 18254 18255 viewPort = dom.getViewPort(editor.getWin()); 18256 18257 // scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs 18258 y = dom.getPos(root).y; 18259 if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) { 18260 editor.getWin().scrollTo(0, y < viewPort.y ? y : y - viewPort.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks 18261 } 18262 }; 18263 18264 // Creates a new block element by cloning the current one or creating a new one if the name is specified 18265 // This function will also copy any text formatting from the parent block and add it to the new one 18266 function createNewBlock(name) { 18267 var node = container, block, clonedNode, caretNode; 18268 18269 block = name || parentBlockName == "TABLE" ? dom.create(name || newBlockName) : parentBlock.cloneNode(false); 18270 caretNode = block; 18271 18272 // Clone any parent styles 18273 if (settings.keep_styles !== false) { 18274 do { 18275 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) { 18276 clonedNode = node.cloneNode(false); 18277 dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique 18278 18279 if (block.hasChildNodes()) { 18280 clonedNode.appendChild(block.firstChild); 18281 block.appendChild(clonedNode); 18282 } else { 18283 caretNode = clonedNode; 18284 block.appendChild(clonedNode); 18285 } 18286 } 18287 } while (node = node.parentNode); 18288 } 18289 18290 // BR is needed in empty blocks on non IE browsers 18291 if (!tinymce.isIE) { 18292 caretNode.innerHTML = '<br>'; 18293 } 18294 18295 return block; 18296 }; 18297 18298 // Returns true/false if the caret is at the start/end of the parent block element 18299 function isCaretAtStartOrEndOfBlock(start) { 18300 var walker, node, name; 18301 18302 // Caret is in the middle of a text node like "a|b" 18303 if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) { 18304 return false; 18305 } 18306 18307 // If after the last element in block node edge case for #5091 18308 if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) { 18309 return true; 18310 } 18311 18312 // If the caret if before the first element in parentBlock 18313 if (start && container.nodeType == 1 && container == parentBlock.firstChild) { 18314 return true; 18315 } 18316 18317 // Caret can be before/after a table 18318 if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) { 18319 return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start); 18320 } 18321 18322 // Walk the DOM and look for text nodes or non empty elements 18323 walker = new TreeWalker(container, parentBlock); 18324 18325 // If caret is in beginning or end of a text block then jump to the next/previous node 18326 if (container.nodeType == 3) { 18327 if (start && offset == 0) { 18328 walker.prev(); 18329 } else if (!start && offset == container.nodeValue.length) { 18330 walker.next(); 18331 } 18332 } 18333 18334 while (node = walker.current()) { 18335 if (node.nodeType === 1) { 18336 // Ignore bogus elements 18337 if (!node.getAttribute('data-mce-bogus')) { 18338 // Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p> 18339 name = node.nodeName.toLowerCase(); 18340 if (nonEmptyElementsMap[name] && name !== 'br') { 18341 return false; 18342 } 18343 } 18344 } else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) { 18345 return false; 18346 } 18347 18348 if (start) { 18349 walker.prev(); 18350 } else { 18351 walker.next(); 18352 } 18353 } 18354 18355 return true; 18356 }; 18357 18358 // Wraps any text nodes or inline elements in the specified forced root block name 18359 function wrapSelfAndSiblingsInDefaultBlock(container, offset) { 18360 var newBlock, parentBlock, startNode, node, next, blockName = newBlockName || 'P'; 18361 18362 // Not in a block element or in a table cell or caption 18363 parentBlock = dom.getParent(container, dom.isBlock); 18364 if (!parentBlock || !canSplitBlock(parentBlock)) { 18365 parentBlock = parentBlock || editableRoot; 18366 18367 if (!parentBlock.hasChildNodes()) { 18368 newBlock = dom.create(blockName); 18369 parentBlock.appendChild(newBlock); 18370 rng.setStart(newBlock, 0); 18371 rng.setEnd(newBlock, 0); 18372 return newBlock; 18373 } 18374 18375 // Find parent that is the first child of parentBlock 18376 node = container; 18377 while (node.parentNode != parentBlock) { 18378 node = node.parentNode; 18379 } 18380 18381 // Loop left to find start node start wrapping at 18382 while (node && !dom.isBlock(node)) { 18383 startNode = node; 18384 node = node.previousSibling; 18385 } 18386 18387 if (startNode) { 18388 newBlock = dom.create(blockName); 18389 startNode.parentNode.insertBefore(newBlock, startNode); 18390 18391 // Start wrapping until we hit a block 18392 node = startNode; 18393 while (node && !dom.isBlock(node)) { 18394 next = node.nextSibling; 18395 newBlock.appendChild(node); 18396 node = next; 18397 } 18398 18399 // Restore range to it's past location 18400 rng.setStart(container, offset); 18401 rng.setEnd(container, offset); 18402 } 18403 } 18404 18405 return container; 18406 }; 18407 18408 // Inserts a block or br before/after or in the middle of a split list of the LI is empty 18409 function handleEmptyListItem() { 18410 function isFirstOrLastLi(first) { 18411 var node = containerBlock[first ? 'firstChild' : 'lastChild']; 18412 18413 // Find first/last element since there might be whitespace there 18414 while (node) { 18415 if (node.nodeType == 1) { 18416 break; 18417 } 18418 18419 node = node[first ? 'nextSibling' : 'previousSibling']; 18420 } 18421 18422 return node === parentBlock; 18423 }; 18424 18425 newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR'); 18426 18427 if (isFirstOrLastLi(true) && isFirstOrLastLi()) { 18428 // Is first and last list item then replace the OL/UL with a text block 18429 dom.replace(newBlock, containerBlock); 18430 } else if (isFirstOrLastLi(true)) { 18431 // First LI in list then remove LI and add text block before list 18432 containerBlock.parentNode.insertBefore(newBlock, containerBlock); 18433 } else if (isFirstOrLastLi()) { 18434 // Last LI in list then temove LI and add text block after list 18435 dom.insertAfter(newBlock, containerBlock); 18436 renderBlockOnIE(newBlock); 18437 } else { 18438 // Middle LI in list the split the list and insert a text block in the middle 18439 // Extract after fragment and insert it after the current block 18440 tmpRng = rng.cloneRange(); 18441 tmpRng.setStartAfter(parentBlock); 18442 tmpRng.setEndAfter(containerBlock); 18443 fragment = tmpRng.extractContents(); 18444 dom.insertAfter(fragment, containerBlock); 18445 dom.insertAfter(newBlock, containerBlock); 18446 } 18447 18448 dom.remove(parentBlock); 18449 moveToCaretPosition(newBlock); 18450 undoManager.add(); 18451 }; 18452 18453 // Walks the parent block to the right and look for BR elements 18454 function hasRightSideBr() { 18455 var walker = new TreeWalker(container, parentBlock), node; 18456 18457 while (node = walker.current()) { 18458 if (node.nodeName == 'BR') { 18459 return true; 18460 } 18461 18462 node = walker.next(); 18463 } 18464 } 18465 18466 // Inserts a BR element if the forced_root_block option is set to false or empty string 18467 function insertBr() { 18468 var brElm, extraBr; 18469 18470 if (container && container.nodeType == 3 && offset >= container.nodeValue.length) { 18471 // Insert extra BR element at the end block elements 18472 if (!tinymce.isIE && !hasRightSideBr()) { 18473 brElm = dom.create('br') 18474 rng.insertNode(brElm); 18475 rng.setStartAfter(brElm); 18476 rng.setEndAfter(brElm); 18477 extraBr = true; 18478 } 18479 } 18480 18481 brElm = dom.create('br'); 18482 rng.insertNode(brElm); 18483 18484 // Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it 18485 if (tinymce.isIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) { 18486 brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm); 18487 } 18488 18489 if (!extraBr) { 18490 rng.setStartAfter(brElm); 18491 rng.setEndAfter(brElm); 18492 } else { 18493 rng.setStartBefore(brElm); 18494 rng.setEndBefore(brElm); 18495 } 18496 18497 selection.setRng(rng); 18498 undoManager.add(); 18499 }; 18500 18501 // Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element 18502 function trimLeadingLineBreaks(node) { 18503 do { 18504 if (node.nodeType === 3) { 18505 node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, ''); 18506 } 18507 18508 node = node.firstChild; 18509 } while (node); 18510 }; 18511 18512 function getEditableRoot(node) { 18513 var root = dom.getRoot(), parent, editableRoot; 18514 18515 // Get all parents until we hit a non editable parent or the root 18516 parent = node; 18517 while (parent !== root && dom.getContentEditable(parent) !== "false") { 18518 if (dom.getContentEditable(parent) === "true") { 18519 editableRoot = parent; 18520 } 18521 18522 parent = parent.parentNode; 18523 } 18524 18525 return parent !== root ? editableRoot : root; 18526 }; 18527 18528 // Adds a BR at the end of blocks that only contains an IMG or INPUT since these might be floated and then they won't expand the block 18529 function addBrToBlockIfNeeded(block) { 18530 var lastChild; 18531 18532 // IE will render the blocks correctly other browsers needs a BR 18533 if (!tinymce.isIE) { 18534 block.normalize(); // Remove empty text nodes that got left behind by the extract 18535 18536 // Check if the block is empty or contains a floated last child 18537 lastChild = block.lastChild; 18538 if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) { 18539 dom.add(block, 'br'); 18540 } 18541 } 18542 }; 18543 18544 // Delete any selected contents 18545 if (!rng.collapsed) { 18546 editor.execCommand('Delete'); 18547 return; 18548 } 18549 18550 // Event is blocked by some other handler for example the lists plugin 18551 if (evt.isDefaultPrevented()) { 18552 return; 18553 } 18554 18555 // Setup range items and newBlockName 18556 container = rng.startContainer; 18557 offset = rng.startOffset; 18558 newBlockName = settings.forced_root_block; 18559 newBlockName = newBlockName ? newBlockName.toUpperCase() : ''; 18560 documentMode = dom.doc.documentMode; 18561 18562 // Resolve node index 18563 if (container.nodeType == 1 && container.hasChildNodes()) { 18564 isAfterLastNodeInContainer = offset > container.childNodes.length - 1; 18565 container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container; 18566 if (isAfterLastNodeInContainer && container.nodeType == 3) { 18567 offset = container.nodeValue.length; 18568 } else { 18569 offset = 0; 18570 } 18571 } 18572 18573 // Get editable root node normaly the body element but sometimes a div or span 18574 editableRoot = getEditableRoot(container); 18575 18576 // If there is no editable root then enter is done inside a contentEditable false element 18577 if (!editableRoot) { 18578 return; 18579 } 18580 18581 undoManager.beforeChange(); 18582 18583 // If editable root isn't block nor the root of the editor 18584 if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) { 18585 if (!newBlockName || evt.shiftKey) { 18586 insertBr(); 18587 } 18588 18589 return; 18590 } 18591 18592 // Wrap the current node and it's sibling in a default block if it's needed. 18593 // for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td> 18594 // This won't happen if root blocks are disabled or the shiftKey is pressed 18595 if ((newBlockName && !evt.shiftKey) || (!newBlockName && evt.shiftKey)) { 18596 container = wrapSelfAndSiblingsInDefaultBlock(container, offset); 18597 } 18598 18599 // Find parent block and setup empty block paddings 18600 parentBlock = dom.getParent(container, dom.isBlock); 18601 containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null; 18602 18603 // Setup block names 18604 parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 18605 containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 18606 18607 // Handle enter inside an empty list item 18608 if (parentBlockName == 'LI' && dom.isEmpty(parentBlock)) { 18609 // Let the list plugin or browser handle nested lists for now 18610 if (/^(UL|OL|LI)$/.test(containerBlock.parentNode.nodeName)) { 18611 return false; 18612 } 18613 18614 handleEmptyListItem(); 18615 return; 18616 } 18617 18618 // Don't split PRE tags but insert a BR instead easier when writing code samples etc 18619 if (parentBlockName == 'PRE' && settings.br_in_pre !== false) { 18620 if (!evt.shiftKey) { 18621 insertBr(); 18622 return; 18623 } 18624 } else { 18625 // If no root block is configured then insert a BR by default or if the shiftKey is pressed 18626 if ((!newBlockName && !evt.shiftKey && parentBlockName != 'LI') || (newBlockName && evt.shiftKey)) { 18627 insertBr(); 18628 return; 18629 } 18630 } 18631 18632 // Default block name if it's not configured 18633 newBlockName = newBlockName || 'P'; 18634 18635 // Insert new block before/after the parent block depending on caret location 18636 if (isCaretAtStartOrEndOfBlock()) { 18637 // If the caret is at the end of a header we produce a P tag after it similar to Word unless we are in a hgroup 18638 if (/^(H[1-6]|PRE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') { 18639 newBlock = createNewBlock(newBlockName); 18640 } else { 18641 newBlock = createNewBlock(); 18642 } 18643 18644 // Split the current container block element if enter is pressed inside an empty inner block element 18645 if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) { 18646 // Split container block for example a BLOCKQUOTE at the current blockParent location for example a P 18647 newBlock = dom.split(containerBlock, parentBlock); 18648 } else { 18649 dom.insertAfter(newBlock, parentBlock); 18650 } 18651 18652 moveToCaretPosition(newBlock); 18653 } else if (isCaretAtStartOrEndOfBlock(true)) { 18654 // Insert new block before 18655 newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock); 18656 renderBlockOnIE(newBlock); 18657 } else { 18658 // Extract after fragment and insert it after the current block 18659 tmpRng = rng.cloneRange(); 18660 tmpRng.setEndAfter(parentBlock); 18661 fragment = tmpRng.extractContents(); 18662 trimLeadingLineBreaks(fragment); 18663 newBlock = fragment.firstChild; 18664 dom.insertAfter(fragment, parentBlock); 18665 trimInlineElementsOnLeftSideOfBlock(newBlock); 18666 addBrToBlockIfNeeded(parentBlock); 18667 moveToCaretPosition(newBlock); 18668 } 18669 18670 dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique 18671 undoManager.add(); 18672 } 18673 18674 editor.onKeyDown.add(function(ed, evt) { 18675 if (evt.keyCode == 13) { 18676 if (handleEnterKey(evt) !== false) { 18677 evt.preventDefault(); 18678 } 18679 } 18680 }); 18681 }; 18682 })(tinymce); 18683 18684